Repository navigation
Conversation
7d22f23 to
efc8cd3
Compare
* refactor(ui): keep execution details in the conversation flow Keep the process disclosure and running/settled lifecycle, but remove the outer card, height caps, nested scrolling, fade measurement and zoom state. Supporting activity should not need a second viewport or an extra action to read everything after expanding it. Trade a bounded long-process view for one transcript scroll path. Tool and reasoning details, answer identity, selection and footer status stay unchanged. Native disclosure motion retains overflow: clip without a scroll container. Remove two zoom-only unit tests and the zoom story. Extend the existing cold-scroll story to require full process layout; remove nested-scroller avoidance and the oversized-answer workaround from geometry coverage. Verified UI build/tests, UI and Storybook typechecks, format/lint, Astryx inventory, 11 focused Electron/CDP story renders and all three geometry scenes with --assert-stable. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Generated-by: Devin * fix(ui): restore elapsed timing to the process disclosure Removing the bounded card alone preserved the earlier move of all timing to the footer. Keep live elapsed time beside the current process, then replace it with the recorded total duration on settlement. The last assistant segment owns this once; pure replies keep footer timing, and settled status and finish timestamps remain below the answer. Reuse the existing clock and duration copy rather than introducing another timer or persisted state. Retry and observation-loss gates still suppress false activity. Existing localized footer labels now accept omitted duration so the header and footer do not repeat it. Extend the existing lifecycle regression: one footer clock hands off to the process, advances from the same start time, then freezes at recorded duration without remounting the answer. Confirmed red before implementation and green after; updated steering, failure, locale, pure-reply and browser coverage. UI suite, typechecks, format/lint and 13 focused Electron/CDP renders pass. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Generated-by: Devin * feat(ui): fold activity batches between process commentary Keep the timed whole-turn disclosure while reducing the rows it exposes: consecutive thinking and tools share one compact batch, and commentary remains outside each batch. Astryx Collapsible owns the trigger and visibility; existing reasoning and tool details remain the second reveal, with tool calls rendered individually instead of introducing another tool-group layer. Derive batches from the existing process projection and anchor them to preceding text. Stable wrappers retain reader choices as items stream, neighboring tools disappear or the outer turn folds. Uniform batch wrappers also handle the initial single item without changing detail identity when more work arrives. Visible summaries carry current or latest action and failure evidence; hidden details stop animating. Removed an unnecessary grouping memo after verifying behavior without it. Extend existing production-path tests rather than adding another suite. Grouping regression failed before the change; streamed boundaries, detail identity, redaction, observation loss and timing now pass in the 531-test UI suite. The existing expanded-process story opens a mixed batch and a real Read result, then checks collapsing and answer selection. Thirteen Electron/CDP story renders, three geometry scenes, typechecks, format/lint and surface inventory pass. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Generated-by: Devin * revert(ui): defer activity batch presentation Revert 5836cb8 at the user request: mixed activity batches need more design work. Keep this PR focused on the previous timed inline process disclosure rather than expanding its interaction scope. The resulting tree exactly matches 98c0d8a: live timing in the process header, recorded duration on completion, original reasoning/tool rows, no outer card or nested scrolling. Verified 531 UI tests, format/lint, Storybook typecheck/build, surface inventory and 13 focused Electron/CDP renders. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Generated-by: Devin * test(ui): drop the ScrollableArea stand-ins the revert orphaned The Proxy getComputedStyle stubs, auto overflow values, and non-zero client-box defines were added so Astryx useScrollableArea could measure the capped process body. With that primitive gone, the comments describe a dependency nothing consumes. Restore the pre-5511 minimal forms: a plain visible-overflow stand-in for the transcript wheel-routing walk, and a parameterless domRoot without geometry defines. reachesTranscript reads overflowY/overscrollBehaviorY and treats visible nodes as pass-through, unchanged. 51 ui and 99 desktop harness-consumer tests pass. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Generated-by: Devin --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Replace the historical SQLite fixture with SQL while preserving migration coverage, reject SQLite binaries from source candidates, clarify bundled attribution, and document release review and replacement steps. Generated-by: Codex
Remove the composer footer's Git-branch chip and the read pipeline that fed it (apache#5487): the chip was that pipeline's only consumer, so nothing is left stranded. The branch is ambient session state the agent owns, not a parameter of the send, so the composer's control row is the wrong home for a read-only value there — and it was the one item on that row drawn as a hand-rolled span instead of an Astryx primitive. The workbar's Review face names the current branch once apache#5120 lands; this lands first so the two never show the same fact twice. Migration: none. Between this merge and apache#5120's there is no surface naming the branch — a deliberate gap over duplicating the readout. apache#5120 needs a trivial rebase on git-review-main.ts and packages/core/src/git-review.ts to drop the readGitBranch / GitBranchReadResult it inherited from apache#5487. Refs apache#2171, apache#5487 Generated-by: Maka
…pache#5541) * fix(ui): keep steering a timeline boundary when its step continues A steering_message parked in pendingSteering until an event opening a new step claimed it. Wire order lets the same stepId keep emitting after a steering, so the row was never claimed: post-steering content merged into the pre-steering group and the steering rendered at the timeline tail. The projection now records a steering's position at arrival as a boundary slice in the step list instead of deferring it to a claim rule. Content after a boundary resolves only to slices past it; tool events for an existing row stay on the slice holding it. Settled steering rows the live stream missed splice into the live order by timestamp rather than trailing. Generated-by: Devin * fix(ui): resolve completions to their message slice across steering boundaries A *_complete event finalizes an existing message row, it is not new content. Routing it through the boundary check appended a fresh post-boundary slice while supersedesKind stripped the pre-boundary one, flipping [text, steering] to [steering, text]. Resolve completions to the last slice of the message wherever it sits, trim the complete full text to the post-boundary portion of a continuation slice, and mark earlier slices complete instead of stripping them. replaySafeDelta now always tracks the source end offset so continuation baselines exist for offset-less streams. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): guard completion slicing behind a verified rendered prefix A completion's full text shares the delta stream's coordinates only when it extends them; the runtime adopts a provider reasoningSummaryText when it diverges from the accumulated deltas, so slicing at continuedEndOffset cut real content or blanked the slice. Compare the payload against the concatenated text earlier slices already render and trim only that verified prefix — mismatch lands the payload whole, the same overlap-check the host projector applies to deltas. Two regression tests pin the divergent and shorter-than-offset cases. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…pache#5536) * fix(desktop): settle stale-epoch local messages instead of replaying A Message dispatched in a Host Epoch that is no longer running could never leave the local outbox. The store forbids retargeting the immutable originHostEpoch, so retries replay the original submit, and the running Host answers `outcome_unknown` for any identity without durable proof. The copy looped between `unknown` and another doomed submit, holding the Session's delivery order behind it, while neither cancel nor reconcile could end it. Resolve the identity's real fate from the Host's existing `turn.message.execution.query` instead: durable receipts and steering proofs settle it as accepted, a surviving queued admission yields to the Host queue, and a cancelled or absent identity becomes an explicit non-delivery that releases the queue and offers a removable local copy. An unanswered query stays unresolved and retried, so a Host that is still recovering is never read as never delivered. Generated-by: Maka * fix(runtime-host): report non-admission positively in the execution query Review [P1] on apache#5536: `#resolveMessageExecution` returns `{ kind: 'recovering' }` when no receipt, steering proof, cancellation tombstone, or pending admission names an identity, and `queryMessageExecutions` dropped that case from the result. The desktop then read the omission as "never admitted" and retired the local copy, so the change's own safety promise — an absent answer must never let a user resend a Message the Host may already own — held only for a thrown failure, not for the path that actually produces "no answer". Give the union an explicit `not_admitted` state and emit it when the identity is traceless and the Host holds no in-flight submit for it. Silence then carries exactly one meaning — "cannot say yet" — and the desktop retires a copy only on positive evidence (`cancelled` or `not_admitted`), keeping an omitted identity unresolved and retried. `not_admitted` is a claim that no epoch ever admitted the identity, so it is only sound while no admission write can be in flight. A stale epoch's submit returns `outcome_unknown` before it can commit, which covers the coordinator's own path, but WorkHub writes admission rows without an in-memory submit to observe. The query now enters the Session admission gate the way its sibling `readMessageExecutionDisposition` already does, so the read cannot race an admission write. A settable `failed` state that `wake()` never revisits made that race uncorrectable, which is why the read has to be atomic rather than merely conservative. Adds the `not_admitted` wire state, so the compatibility epoch moves 168 -> 169; epoch-168 peers reject the new state as an invalid frame. Generated-by: Maka * fix(desktop): retire not_admitted side-chat slots and name observer outcomes Review [P2] on apache#5536: `not_admitted` is positive proof a Message can never execute, but `reconcilePendingMessageExecutions` matched it in neither branch, so the identity was neither dropped from `pendingUserMessagesRef` nor used to release the Composer's admission slot. Recovery was asymmetric: `cancelled` cleared the slot automatically, `not_admitted` left the user pressing Stop to clear a slot the Host had already proven dead. Fold `not_admitted` into the terminal branch and rename the set to `retired`, which is what the two states now mean together. Only an omitted identity still means the Host cannot say, and it keeps its slot. The session observer mapped `not_admitted` to `retracted` only by falling through a pre-existing `owned ? ... : 'retracted'` ternary. Name both retracting states so a later addition cannot silently inherit that outcome, and cover the state in the parameterized observer test. Generated-by: Maka
…he#5544) Remove public Nightly download promotion and retire the built-in OpenCode Free provider while preserving stored connections and conversation history. New installations require an explicit model connection; cancelling CLI setup exits cleanly. Remove obsolete free-tier metadata and adapt session and package fixtures to the explicit-model setup. Validated by main CI, four-platform installed CLI checks, and released State Root compatibility. Generated-by: Codex
…pache#5543) * chore(deps): bump @astryxdesign/* from 0.6.1 to 0.6.2 0.6.2 adds an opt-in Markdown math renderer (components.math), a TextInput IME fix for CJK input, ChatToolCalls a11y announcements, and a localized Spinner label. Upstream reworked Markdown.js parse plumbing for the math option (mathParseOptions / hasMathRenderer cache reset), so the settledText / transformSource patch hunks were re-applied by hand onto the new shape; all other patched files were byte-identical between 0.6.1 and 0.6.2. Generated-by: Devin * refactor(ui): render Markdown math through upstream components.math Replace the private-use TOKEN transport with Astryx 0.6.2's official math pipeline: components.math renders parsed math nodes through the same KaTeX configuration, and transformSource now translates the host delimiters into upstream grammar instead of smuggling tokens past the parser. \(…\) inline math is emitted as `$…$` guarded by zero-width spaces so adjacent digits or dollars cannot trip upstream's currency checks, and `\[…\]`/`$$…$$` emit `$$` lines that repeat the enclosing quote/list margin so containers still own them. Bare `$` outside code and link destinations is escaped, preserving the "shell variables and currency stay literal" contract that upstream's bare-$ pairing would otherwise break. Display math on a table-row line degrades to an inline span so `$$` lines never split a row. The regenerated patch adds one upstream fix: trimStreamingArtifacts' unclosed-marker scans ([, *, ~~) now skip completed inline math spans; previously a last-line formula containing those characters was trimmed or auto-closed as Markdown. Generated-by: Devin
* feat(mcp): forward tools/call progress to Host Wire SDK onprogress through McpClientManager.callTool into the CLI capability provider's existing Host progress hook. Drop incomplete or invalid step counts so a noisy server cannot fail the tool. Refs apache#5069 Generated-by: Pi * fix(mcp): harden forwarded tool progress * fix(mcp): accept emitProgress on tools/call progress Desktop in-process MCP tools pass McpToolProvider.emitProgress into McpClientManager.callTool. Only onProgress requested a progress token, so local Desktop tools stayed frozen while TUI/sidecar forwarded steps. Refs apache#5069 Generated-by: Pi
Point client_uri at the Apache product homepage and set software_id to the running clientName so TUI is no longer registered as desktop. Refs apache#5072 Generated-by: Pi
…ache#5532) * perf(desktop): commit the session catalog at the granularity of change sessions:changed already carries the changed row's id, but the shell answered every hint with a full sessions.list() and committed fresh row objects, so one background session's event stream invalidated every row reference and re-rendered the whole AppShell tree each commit (apache#5441). - Add sessions.get (host session.catalog.query{kind:'get'} -> IPC -> preload) with the same runningTurnIds merge and pendingCleanup filter as sessions.list. - handleSessionChange folds same-id hints into one sessions.get per row; a failed row read falls back to a deduped full refresh instead of evicting the row. Membership changes still take the full-list path. - commitSessions reconciles by id and commitPatch upserts one row: published references change iff values change, and a stale snapshot never regresses a row patched to a newer revision. - AppShell subscribes at the granularity it displays (count, active row, membership set, the two draft rows); the rail, archive/tasks pages, turn-request inbox, palette and setting-intent read the catalog where they consume it instead of through a shell-carried sessions array. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * perf(desktop): observe a side chat only while its panel is visible A retained Side Conversation (PR apache#5440) kept its fork observer alive across session switches: mounted-but-hidden panels still received every fork event, ran the live-turn reducers, and re-rendered a transcript no one could see - ~981 DOM mutations/s and ~440 task ms/s per hidden running panel in measurement. The fork's observation period is now the panel's interest period: QuoteCompanionPanel already receives active = visible && selected; the hook releases the observer when it goes false and re-seeds through the existing lost-subscription recovery path when it returns (seeded events replay, then readSettledMessages reconciles the durable transcript). commitFork resolves send readiness without an observer when inactive. While unobserved no new turn can start - the panel is the fork's only writer - so the tab activity indicator can only freeze at "running", which a returning re-seed corrects. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * perf(ui): reconcile transcript timeline and fold entries at item granularity A live turn rebuilt its whole timeline per event, so every memoized entry re-rendered on each delta even when only the streaming tail moved. Extend the turn-level identity contract one level down: reconcileTimelineItems hands back the previous object for every timeline item whose value is unchanged, keyed by timelineItemKey so mid-timeline inserts (steering) do not shift the comparison. reconcileFoldedEntries does the same for foldTimeline's output, matching processing folds by their stable anchor id and children identity; it also refuses to return a stale array when entries leave the fold. With item identity carried through, TurnTimelineEntry and ProcessingBlock become memo boundaries and the settled prefix of a long turn — dozens of tool rows and reasoning blocks — no longer re-renders per token. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * perf(desktop): dedupe onboarding snapshot emits and serialize pulls sessions:changed fired per background message event, and every event drove a full getSnapshot IPC (4+ parallel queries in main) plus a setSnapshot(newObject) that re-rendered AppShellContent at event rate. Two changes on the same publish-iff-value-changed invariant: - setSnapshot now publishes only when the render projection changes. `sessions` is excluded from the projection: it is boot-time seed data (the session catalog is the live authority) whose rows churn per event; everything onboarding UI renders — state, milestones, connections, defaultSlug, chatModelChoices, sessionSendOutcomes — still propagates. Call-time refs stay unconditionally fresh. - pulls are serialized: an invalidation while one is in flight sets a dirty bit and collapses into a single follow-up, so IPC rate tracks pull latency instead of event rate. The inflight clear lives inside the loop so no microtask window can swallow an invalidation. Measured under ~124 sessions:changed/s: AppShell-level chrome writes drop out of the hot path entirely once combined with the palette subscription sink. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * perf(desktop): sink the palette session subscription into its consumer AppShellContent subscribed selectPaletteSessions at the shell level even though the data only feeds command-palette session rows. Background session churn (activityAt reorder, isFlagged flips) emitted a new visibleSessions per commit and re-rendered the whole shell subtree — ~650 DOM attribute writes/s under ~124 sessions:changed/s. The subscription now lives inside useAppShellCommands in the overlay layer, where the list is consumed; commandOptions carries hiddenSessionIds instead of a materialized session array. While the palette is closed the selector is a constant-empty function, so catalog churn cannot emit at all — the residual 'other' bucket under background steering drops to ~23 mutations/s, all real status-dot updates. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(desktop): sweep session retirement against the committed catalog `handleSessionChange` fed the fetch result of a `sessions:changed` event into `retiredSessionIds`, whose contract is the complete catalog. On the single-row path the result is one row, so every background update retired the selected session and cleared its transcript. The sweep now reads `sessionsRef`, which mirrors the catalog after commit; both refresh promises resolve after their commit, so the timing is safe. The handler moves to a leaf module so the regression test can exercise the production code path without mounting the hook. Reported by kabi-sol on PR apache#5532. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * perf(desktop): sink the stale-session selector into the rail provider The shell subscribed `selectStaleSessionIds` only to pass the set through to `SessionNavigationProvider` — a whole-tree scope for rail-only state, and a new call site the apache#4109 hook gate rejects. The provider now selects it from the catalog it already owns, taking `sessionSendOutcomes` as the prop instead. The three selector calls that remain in the shell body — session count and the two revision-draft rows — are ones the shell genuinely reads, so they are recorded in the hooks inventory rather than moved. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * perf(desktop): publish the catalog only when its content changed `commitSessions`/`commitPatch` bumped `revision` and replaced the snapshot even when every row was reused, so a no-op event still notified every subscriber. Now a commit that reuses all rows returns early — except the first commit, since revision 0 means "no authoritative observation" and an empty list is still one. The row equality check also switches from `summaryValuesEqual` to the fail-closed `valuesEqual` shared with `@maka/ui`, so a non-plain field can never compare equal by walking zero keys. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(desktop): make the onboarding dedup key exhaustive `onboardingSnapshotProjectionEqual` compared a hand-maintained field list, so a new `OnboardingSnapshot` field would silently drop out of the dedup key and stop publishing. A `satisfies` witness over `Exclude<keyof OnboardingSnapshot, 'sessions'>` makes a missing field a compile error. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(desktop): share the session-id set comparator across features sessionIdSetsEqual lived in the conversation feature, so the navigation provider importing it crossed the feature boundary the renderer architecture ledger forbids. It is a leaf comparison over session ids — move it to src/shared/ where both features can reach it. Regenerates the architecture ledger for the new/changed files on this branch (session-change-effects module, the catalog selectors' new call sites, sessions.get bridge path). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(desktop): move session-catalog infrastructure into contracts The strict-base ratchet forbids new feature-to-legacy edges and any capability growth in legacy files, so the catalog machinery moves to the layers that own it: external-store state, selectors and change effects live under application/contracts/session-catalog, and the window.maka.sessions patch drain lives on the platform adapter. Root modules keep their import surface as re-export shims; every feature now imports the contracts paths, which deletes all 15 budgeted feature-to-legacy catalog edges rather than adding new ones. Subscriptions sit at their consumption points: the command palette selects its session rows internally, the archived-tasks page receives sessions through a render-prop component, and the revision-draft watch runs inside CatalogRowWatch. AppShell gains no hooks — its inventory drops to 36 hooks / 60 call sites — and no legacy file grows tokens, hooks, bridge paths or dependencies relative to the merge base. * chore(desktop): drop legacy catalog shims and dead exports Knip flagged five root re-export shims whose last consumers can all point at application/contracts/session-catalog directly, so the compatibility layer is no longer earning its keep. Repoint the six remaining importers and delete the shim files. Also remove selectSessionCount/selectCatalogRevision (no consumers; the revision fence reads catalog.getState() directly) and drop sessionMatchesRail from the session-navigation barrel while keeping the testing.ts export the controller test uses. * fix(desktop): fence revision-draft retirement against catalog admission lag CatalogRowWatch reported a just-created revision owner as absent the moment sessionIds flipped, and the shell retired the draft on that absence — clearing the edited text and silently failing the send. The owner's created-row read is asynchronous, so absence at watch-switch time is admission lag, not removal. The watch now distinguishes three states per id: observed, removed by a targeted read (commitPatch(id, null) tombstones it — a list omission cannot testify about a row it predates), and pending. Retirement only fires for observed-or-removed rows, keeping genuine deletion cleanup intact. A patch-admitted row could still be evicted by a list snapshot taken before the admission, so commitSessions gains an observedAtRevision fence: rows confirmed after the snapshot's observation point survive the commit, the membership-level analogue of the per-row staleness fence. The refresher stamps each fetch with the catalog revision at issue time. The composed sequence — owner switch before admission, tombstone, stale list, authoritative eviction — is pinned in session-change-retirement.test.ts. * fix(desktop): keep locally-owned Sessions out of targeted catalog reads sessions.get can only answer for Host-owned rows, while the merged list also carries pending Sessions the local store still owns. Emitting a sessions:changed event with a pending Session id routed it through the row-level drain, whose sessions.get returned null and evicted the row. The sweep then retired the selected Session and cleared the transcript, leaving the shell on the new-task hero after every first send. The local-change emitter now drops the row id while the store holds the creation intent, so pending changes refresh the merged list; admission clears the marker and the targeted path resumes. On the renderer side, a row-level read retires only its own tombstoned id: an unrelated event can no longer read a still-uncommitted selection as deletion. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…pache#5471) * fix(desktop): latch transcript replica read failures instead of re-arming A transcript page-read rejection used to re-arm unconditionally: advance()'s finally queued another catch-up whenever the announced watermark still led the durable one, so a replica bound to a dead subscription spun an unbounded microtask rejection storm — observed pinning a Maka main process near 75% CPU and starving IPC. Runtime Host read failures are permanent for the subscription a replica is bound to, so the first one is now latched: later advance() calls and transcript reads reject with that same error — ahead of the generic closed state — and only a settled catch-up re-arms, once, to cover a watermark that moved while it ran. Non-subscription failures still propagate unlatched so a transient page read can retry on the next announced frame, and renderer delivery failures keep their existing path and never latch. advance() also loses its explicit target: the subscription already records the newest announced watermark before handing out the frame, so the replica reads it from the handle rather than mirroring it in a second field. Recovery stays with the subscription owner, which replaces the replica or the subscription by its existing classification. Generated-by: Devin * fix(desktop): dispatch subscription pump failures before the close handshake A frame handler rejection inside the pump's for-await only reached the catch block after the loop's implicit return(), which awaits the subscription's close acknowledgement — under a rejection storm that handshake never lands, so failure handling never ran and the failed attempt's replica stayed armed. Frame errors are now dispatched inside the loop: the attempt is failed and replacement or terminal teardown is kicked off before the iterator is left, so teardown no longer queues behind the close handshake. Generated-by: Devin * refactor(desktop): reseed evicted transcript replicas on the live subscription Eviction recovery used to replace the whole subscription — a new handshake, a new replica, and retiring-subscription frame buffering — even though the subscription was still alive and only the durable tail was missing. Reseeding reads the current tail at the live watermark on the same handle, installs it through the same path as prepare(), and lets advance() cover whatever was committed during the fetch. The owner swaps attempt.replica only while the attempt is unchanged, so a concurrent recovery is never displaced; consumers reset through the existing generation mechanism. Deletes refresh(), the retiring phase, pendingFrames buffering, and the MAX_PENDING_* constants. The two owner-level tests that exercised refresh directly are replaced by reseed tests covering same-subscription rebuild, catch-up across the fetch window, and supersession by recovery. Generated-by: Devin * test(desktop): deduplicate subscription test fixtures runtimeHostSessionFixture now defaults transcript and events to empty, and hosts the shared AsyncFrameQueue, continuitySnapshot, and transcriptPage that several test files each defined locally. Removes the empty-transcript literals and three duplicate helper definitions. Generated-by: Devin * fix(desktop): commit reseeded transcript replicas inside the owner's swap The reseed swapped attempt.replica and closed the evicted replica while the observer's state still referenced it, installing the new one a microtask later. A transcript_advanced landing in the gap called advance() on a closed replica, whose rejection classified as terminal — the failure class this branch exists to eliminate. A recovery interposing in the same gap closed the freshly swapped replica and left state.replica and attempt.replica permanently diverged. The observer's install now runs as an installReseededReplica dep inside the owner's staleness check, so state.replica moves before the evicted replica closes — the same atomicity activate() has. The failed attempt detaches before the first teardown await so a reseed cannot swap onto a corpse, and discard()/trimDurable() check residency before liveness since recovery windows legitimately leave closed replicas in state. A reseed read failure also had nowhere to go: session.transcript.page not_found — the class recovery exists for — propagated raw and stranded the session on a dead handle. It now routes through #failAttempt exactly like a pump-frame failure, and onChange no longer caches snapshots from replicas that were never installed. Adds the coverage the audit surfaced: recovery on a failed reseed, concurrent-reseed serialization with byte-balance assertions on superseded builds, evicted-replica advance and mid-flight watermark tests, and an observer-level evict-to-reopen test behind a transcriptGlobalCacheMaxBytes dep. Generated-by: Devin * docs(desktop): state the installed-replica liveness invariant state.replica can legitimately hold a non-resident or closed replica — eviction and recovery windows both produce that — but the invariant was implicit, so each consumer encoded its own assumption (discard() and trimDurable() asserted liveness first, which killed unrelated sessions under cache pressure). Writing the legal state set and the single swap authority down where the pointer lives. Generated-by: Devin * fix(desktop): route the reseed commit's catch-up through the attempt The install callback ran consumer resets and the budget pass before moving state.replica, diverging from activate()'s pointer-first order and undercounting the new replica's bytes in that pass. The pointer now moves right after the only throwing steps: a throw still leaves the state on the evicted replica for the owner to close, while resets and accounting see the installed replica. The post-commit catch-up moves out of the callback and into the owner, where its rejection takes the same #failAttempt path as a pump-frame failure. Swallowing it inside the install left a latched-dead replica reporting resident on a quiet stream. Generated-by: Devin * fix(desktop): close the reseed survival gaps found by adversarial review - feed the surviving projector the reseeded tail: rows that went durable while a replica was evicted never reached its durable-message map, so steering suppression and message admissions stayed stale until the next subscription recovery rebuilt the projector - report resident=false for a latched replica: a dead read model now takes the same reseed/recovery path as an evicted one, and ack/read gates stop throwing the latched error into unrelated IPC - let the pump's authoritative error land before reporting a masked 'connection_closed' from a racing reseed fetch — a recoverable close could otherwise be inverted into a terminal teardown - wait out in-flight recovery before a losing reseed returns, so callers pick up the installed replica instead of erroring on the closed one - protect the just-installed replica from its own install-time budget pass - drop dead watermark bookkeeping on evicted replicas — nothing reads a husk's durableThrough now that reseed always builds a new replica Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(desktop): classify masked reads by the subscription's death certificate The fourth adversarial round found two real defects in the previous fixes: - The setImmediate deferral only covered the case where the pump's report was already queued; a pump blocked inside acceptFrame's round-trip still lost to the masked connection_closed. The subscription already knows its own death reason — expose terminalError/closedReason and let #failAttempt prefer that certificate, which heals the reseed, acceptFrame, and post-commit paths deterministically and makes the deferral unnecessary. - The latch caught every RuntimeHostOperationError, so a transient blip on the swallowed post-settle re-arm became a sticky terminal on the next frame — a regression over main's silent retry. Narrow the latch to failures that prove the subscription or its transcript context is dead. Also assertTranscriptReadable now throws the recorded terminal error when one exists, so every transcript reader sees the real failure. Tests: the masked-close classification test was vacuous (the pump's chain is strictly shorter, so it could never exercise the race) — it is now driven by the certificate, plus a terminal-removal variant. Add coverage for loser-branch terminal propagation, transient operation failures staying unlatched, acknowledgeTranscriptTail on a latched replica, and a latched replica reseeding through recovery end to end. Generated-by: Devin * fix(desktop): harden subscription death classification under adversarial review - Record the Host close reason before the queue offer so a full client queue cannot discard session_removed/access_revoked as slow_consumer. - Prefer closedReason unconditionally over terminalError: the Host's own death statement is recorded first, so it always outranks the synthesized connection_closed mask a dying transport stores later. - Normalize subscription.open/not_found at the terminal boundary to the removed-session path so a recovery losing to a deletion still emits sessions:changed deleted instead of a generic observation error. - Exempt TranscriptCacheCapacityError from attempt failure: the rolled- back charge is not a subscription failure, and tearing a healthy subscription down under memory pressure frees nothing. #failAttempt now reports whether it consumed the failure so the pump keeps consuming absorbed errors instead of exiting unconditionally. - Keep throwing steps before the pointer move in activate() and release prep bytes before clearing the adoption flag, matching the install contract. - Add the required certificate members to the CLI test fakes (the new interface members broke the packages/cli build) and cover terminalError/closedReason on the real subscription client. Generated-by: Devin * fix(desktop): invert the failure channel's default to absorb, not death The classification at #failAttempt was "unclassified error = terminal", so every new failure shape reaching the channel needed an exemption or it tore a healthy subscription down — the capacity RangeError, transient Host operation errors through the pump, and the next undiscovered shape. The invariant is now stated at the boundary: an attempt dies only on positive evidence of subscription death — a recoverable subscription failure or a subscription-scoped error — and unclassified errors are absorbed, but only for read-only callers whose work is safe to retry on the next frame (a catch-up's watermark is still ahead). Non-transcript frame failures may be committed mutations that will not be replayed, so they still die loudly. #failAttempt reports whether it consumed the failure so the pump keeps consuming absorbed errors. - Drop TranscriptCacheCapacityError: the whitelist makes a dedicated exemption type unnecessary. - Make terminalError/closedReason optional on the public RuntimeHostSessionSubscription — they are owner introspection, not contract, and fakes that never produce a certificate need not declare them. Reverts the four CLI test-fake additions. - Keep the certificate recording order (reason before the queue offer), closedReason's unconditional precedence, the open/not_found deleted normalization, and the activate/adopt ordering fixes. Generated-by: Devin * refactor(desktop): ablate redundant diff from the replica change Reverts adoptResidentAccounting to the flag-then-release order: the release can only throw on a prior accounting corruption, which a retry cannot repair, so guarding it was dead weight. Drops the generic-Error no-latch test — its protection is strictly subsumed by the transient-operation-error case, which sits on the instanceof boundary and additionally fails if the latch stops checking the operation code. Generated-by: Devin * refactor(desktop): collapse the death certificate into one authority The second ablation pass removed the pieces the audit proved derivable: - one certificate getter (`deathCause`) replaces the terminalError/ closedReason pair; the reason-to-error taxonomy moves to the client package where the recording order lives - SubscriptionAttempt.phase was the candidate pointer restated; the pump and failAttempt read #candidate directly - the reseed catch's instanceof pre-filter was a third classifier; every error now goes through failAttempt and only unclassified ones reach the caller - reseedTranscriptReplica returns void; both callers re-read state - owner deps no longer take a clock it never read - the evicted replica's watermark fallback was dead: throughSequence null already means the newest page Two dominated owner tests were removed and replaced by the missing half of the absorb contract: a committed non-transcript frame failure must still terminate the attempt. Generated-by: Devin --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…an (apache#5531) * feat(core): carry the anchor's transcript index on a Recall passage A Recall passage names its anchor by message id, which is enough to identify the message but not to point at it: the transcript reader addresses rows by sequence, so a caller holding only an id has to page the transcript to find the anchor. That is the whole cost a UI pays when it wants to open a result and scroll to it. `buildPassage` already locates the anchor with `findIndex` over the same transcript it assembles the passage from, so the index is in hand; carry it as `sequence`, the coordinate `runThreadSearch` already reports as `SearchResultTarget.sequence`. `expandRecallPassage` rebuilds through the same function, so a widened passage keeps the coordinate. Purely additive: no caller changes, and the field is defined on every passage that exists because `buildPassage` returns undefined when the anchor is not found. Generated-by: Claude Code <noreply@anthropic.com> * refactor(core): move the transcript-search primitives out of thread-search Recall and the Agent's global history search both fold transcript text and classify what they matched, so they import `foldForMatch`, `MAX_SESSIONS_SCANNED`, and `threadSearchMatchKind` from `thread-search.ts`. That put a second retrieval implementation's home on the path of two surfaces that do not perform a thread search, and it is about to become a dangling import once the Desktop's thread-search lane is replaced. Move exactly those three to `transcript-search.ts` — pure functions and one constant, no storage, no privacy state, no search envelope — and have `thread-search.ts` re-export them so its published surface is unchanged. `recall` and the Host composition import the new module directly. No behavior change: recall's suite and the thread-search text-projection suite both pass unchanged. Generated-by: Claude Code <noreply@anthropic.com> * feat(runtime-host): serve recall over the protocol as recall.query Recall has only been reachable by the model, through the `Recall` tool. Its implementation lives in the Host because the corpus does: the Session manager, the distilled-fact store, and the material fetch are all Host-owned, so a Client cannot perform a recall without dragging every transcript across the boundary. Expose it as `recall.query`. The coordinator holds no retrieval logic — it calls `runRecall` and projects the answer — and it is constructed from the same `recallDeps` the model's tools use, so a Client search and a model recall cannot diverge in what they can see. No turn is excluded here: a tool call runs inside a turn and must not echo that turn back, but a Client search is not inside one. The call this replaces, the Desktop's `runThreadSearch`, scanned up to 200 Sessions and ranked by substring order. Recall narrows candidates, ranks with BM25, and returns distilled facts and context-carrying passages alongside the hits. Its passages now carry the anchor's transcript index, which is what a Client needs to scroll to a result. Compatible extension, declared rather than bumped: a peer that predates `recall.query` never sends or receives one, so every existing frame decodes unchanged. Generated-by: Claude Code <noreply@anthropic.com> * feat(desktop): search history through recall instead of a local scan The Search modal answered by pulling every Session's transcript into the main process and scanning it: up to 200 Sessions per page, ranked by substring order, returning a flat list of matched lines. Recall already answers the same question better — it narrows candidates in storage, ranks with BM25, and returns context-carrying passages — and it runs where the corpus lives. Switch the lane end to end. `search:recall` replaces `search:thread`; the preload fan-out keeps its shape, asking every ready Owner Host and interleaving the answers, because a score from one Host's corpus is not comparable to another's and the merge must not pretend otherwise. Each Host scans only its own history, which is also why this stops hauling transcripts across the boundary. The modal keeps its shell, its cancellation semantics, and its navigation — a passage carries its anchor's transcript index, so a click still lands on the turn that matched. One user-visible change worth naming: recall matches literal terms, OR combined, so a typed phrase is split into its distinct words. A multi-word query ranks passages containing more of them higher rather than requiring the exact phrase. Delete the old lane: `runThreadSearch`, its main-process IPC, its preload fan-out, and their tests. `foldForMatch`, `MAX_SESSIONS_SCANNED`, and `threadSearchMatchKind` move to `transcript-search.ts` because recall and the Agent's global history search still share them. Generated-by: Claude Code <noreply@anthropic.com> * test(search): cover the recall lane, and fix the cancellation it caught The replacement deleted four test files and added one, which left every new module untested: the IPC relay, the multi-Host merge, the query-to-terms split, and the coordinator's projection. That is the wrong trade — the old lane's tests were the only thing pinning behavior these modules now own. Cover them, and keep the assertions on what each layer decides rather than on the framework it runs in: - the IPC relay: what it refuses, what it answers when the Host is gone, and that an abandoned request stops being ours to wait on; - the merge: interleaving, partial failure, and that a score from one corpus is never compared against another's; - the query split: a phrase becomes its distinct terms, capped and deduped; - the coordinator: end-to-end over Host deps, the wire projection, and that a Client search excludes no turn because it is not inside one. Writing the cancellation test found a real defect: the handler marked a boolean and then awaited the Host, so a cancelled search held the IPC channel open until a Host answered a question nobody was waiting for. Race the read against the abort instead, and keep the reconnect policy's rethrow visible rather than swallowing it. Generated-by: Claude Code <noreply@anthropic.com>
Report + per-task CSV for the three-arm file-editing tool contract experiment on Terminal-Bench 2.1 with deepseek-v4-flash: str_replace_editor vs @deepseek-ai/dsh-tool-fs vs a repo-authored apply_patch plugin. No detectable difference (56/56/53 of 86 scored tasks); the report states this as a failure to detect, not equivalence. Docs only. Generated-by: Claude Code
…n projects Merged after review. The Settings remote naming inconsistency is tracked as a non-blocking follow-up comment.
Remove the standalone Deep Research workflow while preserving historical chats, reports, stored events, and access credential compatibility. Generated-by: Codex
…ache#5550) * perf(desktop): stop republishing catalog bookkeeping to the shell Row-level catalog patches still republished the whole active row to the shell on every event: flag/unflag, unread and preview bookkeeping all committed a fresh row object, and the observation channel re-sent a fresh execution projection per frame. Each republish re-rendered the chat surface — measured at ~2.7k rendered fibers per flag toggle, mostly astryx controls rewriting DOM attributes. - Gate the shell's whole-row reads with a field-aware equality that skips rail-only bookkeeping (activity, unread, flag, preview, revision, timestamps, subagent runtime) and compares every other field by value; fields added later republish until proven rail-only, so the failure direction is an extra render rather than a stale value. - Compare execution projections by value before publishing: the channel resends an equivalent object on unrelated metadata events. - Feed composer mentions from the renderer catalog instead of a full sessions.list() IPC per sessions:changed; drop the now-unused list and subscribeChanges ports from the conversation service surface. - Subscribe the browser panel only while visible and reseed via getState on the way back; keep artifact poll results identity-stable when the list is unchanged. Measured (Electron + react commit probe, 8 flag toggles on the active row): 73 commits / ~22k rendered fibers -> 20 commits / 372 fibers; the row diff stays isFlagged+revision and remaining renders are the rail's own flag/selection update. Generated-by: Devin * perf(desktop): resolve review findings — export the row equality, mount the catalog above services - Export shellSessionRowEqual through the conversation feature's testing surface; the test no longer deep-imports the controller file. - Give the session catalog a React context mounted once in composition (createDesktopFeatureServices owns the instance): providers read it via useSessionCatalogController instead of a prop drilled through app-shell, keeping app-shell.tsx byte-identical to the ratchet baseline. - Pin the wiring, not just the predicate: a mounted selectSessionById subscriber survives a rail-only patch and re-renders on a name change; setExecution holds publication for a fresh-but-equal projection; a field outside the rail-only list fails closed. - The slash-menu story drives its session-update refresh through a catalog commitPatch (thinkingLevel bump) instead of a subscribed 'updated' event. Generated-by: Devin * test(desktop): mount the session catalog provider in the workspace identity test useAppShellSessionWorkspace reads the catalog from SessionCatalogContext now, so the probe needs the provider the composition mounts in production. Generated-by: Devin
…pache#5565) `skill-draft-lifecycle` fails on most branches right now, including main, with "Skill 调用失败,消息未发送" never becoming visible. Two independent races sit between disabling the Skill and pressing Enter, and each one makes the rejection the journey asserts never render: - The toggle goes through the raw `window.maka.skills.setEnabled` bridge, not the Skills page, so nothing re-fetches the composer's `/` source. Until Runtime's projection drops the Skill it is still invocable, the send resolves it, and the send succeeds. - Picking the Skill left the `/` menu open. While the editable reports `aria-expanded`, the composer swallows Enter as menu acceptance rather than sending, so the draft is never submitted at all. Wait for both before pressing Enter: the editable back to `aria-expanded="false"`, and the Skill gone from `listInvocable`. The second uses a new helper beside the existing `waitForInvocableSkills`, which already polls the same authoritative projection for the opposite transition. Generated-by: Maka
* chore(provider): remove the Command Code GO provider Command Code GO did not use a published API. It sent requests to the official CLI's private `/alpha/generate` endpoint behind the same identity headers that CLI presents (`x-command-code-version`, `x-cli-environment`, `x-taste-learning` and friends), and its account-usage card read the same `/alpha/*` endpoints with the same headers. The provider was withdrawn from the registry for that reason. Removing the provider takes its whole surface with it, because everything else existed only to serve it: - the `commandcode-cli` Runtime adapter, its `/alpha/generate` wire, and the CLI identity headers it forged; - the browser-assisted sign-in (`commandcode-browser-login`, `commandcode-login-ipc-main`, its renderer flow and section) that minted a key through the CLI's own loopback login; - the account-usage read end to end: the `connection.usage.read` operation and its Host coordinator, the storage `connection_usage` ticket machinery, the `read_usage` ProviderAuthAction, the runtime fetch/parse, the core report types, and the settings usage card; - the transport-acknowledgement gate, which existed only to tell a user that one provider presented another client's identity, and so is now an empty set. The ordinary `commandcode` provider stays: it is a plain API-key connection over the published Provider API, and it keeps the shared reasoning-effort table. The `connection.usage.read` operation is gone, so the compatibility epoch moves 169 -> 170; a peer older than this epoch may still advertise or submit it. Generated-by: Maka * fix(provider): retire Command Code GO instead of removing it Review [P2] on apache#5545: deleting the entry outright leaves an existing connection *unknown* rather than *retired*. `isRetiredProvider` answers false for a type no longer in the registry, so the readiness guard never fires, a connection with a default model and an enabled model list reports `ready: true`, and the send is admitted only to throw "Unknown provider type" deep in model construction — the exact failure `connection-readiness.ts` documents above that guard. Keep the entry with `retired: true` and `runtimeAdapter: unavailable`, the pattern apache#5544 used for OpenCode Free. A stored row stays identifiable and displayable, readiness answers `provider_retired`, and the user lands on the existing `blocked:all_connections_retired` state with its "Add a model connection" call to action. `provider-catalog-contract` already pins the retired set and asserts each entry keeps no Runtime adapter and stays out of the add catalog, so this restores that entry rather than adding new machinery. Also regenerate the Astryx surface inventory, which still listed the two files this PR deletes and failed the CI step ahead of Typecheck. Generated-by: Maka * chore(desktop): drop the browser-login copy left by the GO removal The sign-in section that read `browserLogin` is deleted, so its three locale blocks — including the "sign in with your Command Code account" title and the port-range failure text — are unreachable. Remove them with it. * fix(runtime-host): release the retired usage grant CI on this branch failed in `Qualify durable state against the published baseline`: The released access credential carries grants this build cannot account for: connection.usage.read Removing an operation leaves its grant in every credential a released build already wrote, and `unresolvedPersistedGrants` reports a stored grant that the protocol no longer defines and no migration entry names. The released fixture carries `connection.usage.read`, so the forward roll refused it. Add the `release` entry, which drops the grant on decode and records that its authority went nowhere — the same treatment `turn.regenerate` and `execution.inspect.resolve` got when they were removed. Without it the grant survives in the record and is reported as unaccounted for on every read. Generated-by: Maka
…he#5494) * fix(runtime-host): stop progress views from inheriting attention fields The handoff progress projection spread the previous attention view, so stale fields like reason "retry_required" surfaced during an active update — exactly the misleading staging + retry_required combination reported in the diagnostics of issue 5476. Split HostHandoffView into a discriminated union (attention carries reason, progress carries phase) and build progress views explicitly instead of spreading. Refs apache#5476 apache#5488 Generated-by: Devin * feat(desktop): create the main window before Local Host reconciliation Desktop startup awaited the Local Runtime Host connect/handoff chain before building the window or registering IPC, so first paint was gated on connect, generation checks, and on a version mismatch the full managed update. The manager already registers its reconnecting IPC router on construction, so construct it synchronously, create the main window and local caches immediately, and run start() in the background. A failed first connect now degrades the Local target to unavailable, the same semantics post-start fatals already used, instead of closing the router and quitting; retryLocalStart re-drives it in place with a fresh epoch so the profile entry and local session cache survive, and profile enablement routes the recovery dialog retry to it. The boot no longer opens a launch progress window; the handoff surface still opens on demand until the in-window surface replaces it. Refs apache#5488 Generated-by: Devin * refactor(desktop): mount the renderer without prefetching the onboarding snapshot The pre-mount prefetch (retry + 2.5s timeout + prop plumbing + the workHub bypass) existed only to skip a transient loading frame. Mount immediately instead: the .maka-preload skeleton already covers the load gap, useOnboardingSnapshot pulls after mount and re-pulls on sessions:changed / connections:event, and a failed pull now falls back to the empty-chat surface instead of suppressing it forever. Generated-by: Devin * feat(desktop): surface Runtime Host handoff attention inside the main window Replaces the standalone startup/progress window with an in-window attention surface. Host reconciliation now runs silently in the background; progress stays invisible and only views that need a user decision (attention state) are pushed to the renderer, which renders them as a required dialog with the existing copy-diagnostics action. - New runtime-host-handoff-surface publishes the current handoff view over IPC and routes renderer decisions back to the handoff submit. - New preload bridge (current/subscribe/decide) plus a renderer overlay mounted above AppShell; localized copy follows the existing UiCatalog pattern. - Deletes startup-progress-window.ts, startup-presentation.ts, their test, the onShow hook, the duplicated onUpdateProgress option (the onProgress callback already carries the same phases), and the renderer-architecture allowlist entry. - Exports the formatted handoff presentation type so the main-process surface and renderer share one contract. Generated-by: Devin * refactor(runtime-host): drop the pre-dev.9 file-lock compatibility half-layer Managed updates no longer interoperate with Host operators older than dev.9, so the lease machinery that supervised a legacy child's directory lock goes away: - withLegacyFileUpdateLockLease and its .supervised marker producer are deleted; withProcessLifetimeFileUpdateLock keeps recovering stale .supervised + .lock directory pairs left behind by already-shipped builds and still refuses to steal a live legacy directory lock. - withRuntimeHostManagedServiceLegacyOperatorLeases and the update command's lock-protocol probe, inheritedFds stdio plumbing, and RuntimeHostOperatorInvocation are deleted; retire now runs the current operator directly. The process-lifetime-lock-v1 capability stays in the operator echo so older updaters still detect current operators. Trade-off to call out in review: upgrading FROM a pre-dev.9 Host is no longer a supported path; the retire loses the crash-safety umbrella the inherited leases provided for that case. Generated-by: Devin * test(desktop): cover the Runtime Host handoff overlay in Storybook Exercise the in-window attention surface the same way the bridge drives it in production: replacement consent clicks through to decide(), a retry-exhausted view offers only cancel, and a progress view mounts nothing. Generated-by: Devin * fix(desktop): put the handoff copy-diagnostics action on its own line Text defaults to inline display, which pulled the ghost button onto the last line of the detail paragraph. Generated-by: Devin * fix(desktop): keep background startup alive when the Work Board schema is unverified A degraded Local start resolves instead of rejecting, so the continuation still reaches registerWorkBoardIpc — where require_current throws without a Host-verified schema. The throw aborted the rest of the chain, skipping guest-session restore, interrupted-setup recovery, and remote profile startup. Registration is now isolated and retried when a Local target reaches ready, so a recovered Host completes the board setup too. Generated-by: Devin * fix(desktop): deliver the newest handoff payload when mounting the overlay The snapshot fetch and the push subscription were in flight together; a publish landing between them was overwritten by the older current() response. Subscribe first and let the push win. Generated-by: Devin * refactor(desktop): source the handoff payload type from the bridge contract The surface re-declared the wire shape it sends; the bridge contract is already the single declaration both sides import. Generated-by: Devin * fix(desktop): mount the handoff overlay inside the locale providers useUiLocale() throws without LocaleProvider, which lives inside LegacyAppShell — mounting the overlay at Theme level crashed the renderer root on first paint and the window never reported ready. Mounting it in the provider subtree also scopes the dialog to the main surface instead of the floating WorkHub window. Catches added for the snapshot fetch and the clipboard write, which reject when the document is unfocused. Generated-by: Devin * fix(desktop): keep a pending handoff decision visible behind silent updates Every update claimed the visible slot, so a concurrent handoff's progress view could displace a pending attention decision — indefinitely for manualRecheck views that never republish. Only attention views own the slot now, recency-ordered, and the presentation locale resolves per publish instead of once at boot. Unit coverage for the arbitration and the decide fencing, plus a source guard pinning the overlay mount inside the locale providers. Generated-by: Devin * fix(desktop): offer the default-Host recovery prompt for a failed Local start A permanently failed Local Host left the app alive but stranded: the recovery offer skipped the Local profile outright, so retryLocalStart() was unreachable even though the whole retry machinery behind it was already wired. Local failures now enter the same default-Host recovery loop, with a two-button prompt (Retry / Keep Offline) since "Use Local" is meaningless when Local itself is the one that failed. Generated-by: Devin * refactor(desktop): move the handoff overlay into the runtime-host-management zone A new flat renderer file is forbidden growth under the renderer architecture check and --strict-base rejects it outright, so the overlay moves into the existing Runtime Host feature zone: a `handoff` port on RuntimeHostManagementServices carries current/subscribe/decide, the platform adapter owns the only window.maka access, and the component consumes services through context — which also removes the provider-free window.maka?.runtimeHostHandoff probe that silently no-oped whenever the bridge was not up yet. Clipboard goes through the port like every other copy action in the feature, and a copied toast restores the feedback the retired startup window had. The stories wrap the real services provider and adapter around a fake bridge channel instead of stubbing the component's data source, and the architecture ledger is regenerated. Generated-by: Devin * refactor(renderer): drop the prefetch-era snapshot getters getSessions/getConnections/getDefaultSlug and the refs feeding them were seeding plumbing for the removed onboarding prefetch; nothing calls them any more. Generated-by: Devin * refactor(desktop): activate remote profiles without waiting on Local reconciliation startEnabledProfiles only drives remote targets, but it sat behind the Local Host's whole start lifecycle — a handoff parked on a user decision held every enabled remote profile hostage for the session. Generated-by: Devin * fix(desktop): raise the main window when a handoff asks for a decision The retired startup window called focus() on every attention view; the in-window surface must keep that pull or a required decision waits silently behind a minimized window. One raise per revision keeps repeat updates of the on-screen decision from stealing focus. Generated-by: Devin * fix(desktop): treat pre-ready Host reads as pending, not failed With first paint ahead of Host readiness, mount-time refreshes now race the Host coming up. A getDefault() rejection meant "still connecting", but every background read reported it as a failure: five startup toasts, a stuck memory pill, stale shell settings, and an onboarding snapshot error that cascaded into a bogus connection-refresh error. Gate background refresh reporting on default-Host resolvability and lean on the existing ready-transition re-fire for recovery. Mutations and post-ready failures still surface errors. The onboarding poller defers the same transient rejection via a message check — importing the probe would add a forbidden dependency edge in the architecture ledger. Verified end-to-end on a real Electron boot: first paint ~170ms, renderer mounted ~750ms, Host ready ~1.1s, zero startup error toasts, and the onboarding provider list hydrates on the ready transition. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(desktop): reveal the window on the first painted frame The window stayed hidden until the renderer's first React commit, so the designed .maka-preload loading surface — meant to be the loading UI — was never visible and perceived startup was bounded by React mount (~750-940ms). Electron's ready-to-show fires as soon as the skeleton has painted; routing it through the existing reveal gate shows the window ~500ms earlier while keeping inactive/hidden modes, deferred focus, and the notifyRendererReady/fallback backstops intact. Perceived startup now matches the UI-first goal: the window appears with the loading surface while Runtime Host reconciliation continues in the background. Generated-by: Devin * feat(desktop): fire the window before the Runtime Host module graph runtime-host-boot evaluates ~1100 compiled files before its first statement, so window creation could not start until ~400ms after the app was ready. early-window.ts holds the light slice the window actually needs — storage root, settings, locale, diagnostics, the window controller, the quit coordinator — and fires createWindow as soon as it exists; main.ts imports it first so the remaining Runtime Host graph loads while the renderer is already navigating. Window creation moves from ~400ms to ~210ms on this machine. The login-shell PATH probe now starts at module top and is awaited only where a child process is spawned (Local Host start, remote profiles, MCP) — it no longer sits serially ahead of the window (~100ms on a real launch; e2e fixtures skip it as before). Independent small reads (client instance id, Runtime Host startup) run in parallel. window:notifyRendererReady is not a Host-scoped channel: it is registered on ipcMain by early-window so the renderer's first commit cannot outrun scoped-router registration. The quit coordinator and the window controller keep their exact lifecycle semantics — before-quit aborts in-flight creation, close hooks and diagnostics reach the Runtime Host through boot-context late bindings. Generated-by: Devin * feat(desktop): show the window at construction in active runs The previous commit deferred the Runtime Host module graph until the window existed, but the window still stayed hidden until ready-to-show: the user-visible surface arrived at ~500ms while the OS window itself could have been on screen ~160ms earlier with its theme-matched backgroundColor reading as a launch surface. - `show: revealMode === 'active'` — active runs display the native window at construction; the persisted appearance still picks the right backgroundColor so the first visible frame is theme-correct. Hidden/inactive e2e modes keep show:false and the reveal gate. - Saved-bounds/mkdir/appearance reads run in parallel ahead of the constructor; serialized they cost ~200ms of prelude. - A maximized session restores via maximize() directly in active mode (the window is already shown, so the reveal-gate deferral no longer applies); hidden/inactive runs still defer to markReady. - The firstWindowConstructed boundary moves from construction to the native 'show' event: the heavy module graph may evaluate once the window is on screen without starving the display path, and the launch-settle fallback keeps hidden/failed runs unblocked. Generated-by: Devin * feat(desktop): replace the preload skeleton with a brand surface The fake app-frame shimmer (a card with two gradient bars) read as a broken half-rendered UI rather than a launch screen. Codex's cold start shows the better pattern: a theme-matched window with a centered mark. - `.maka-preload` becomes the traced wordmark (the same MAKA_WORDMARK_PATH the hero and dock icon use) at --maka-brand on the theme background, with a slow opacity breath and a reduced-motion opt-out. - `body` gets the hardcoded theme background (#ffffff / #1c1d21) so the native window's `backgroundColor` hands off to the first frame with no colour step. - The renderer entry contract allowlists the three presentational tags the inline mark needs (svg/g/path); navigation and execution vectors stay closed via the single-module script check and the on*= scan. Generated-by: Devin * feat(desktop): hold the launch surface until the app reports a usable frame The launch overlay moves out of #root into a fixed fullscreen layer, so React mounts underneath it instead of replacing it. AppShell drops it once the bootstrap snapshot resolves and no session view or transcript read is still in flight, which removes the bare-vibrancy window, the partial shell, and the skeleton beats from the startup sequence. main.tsx arms an 8s failsafe so a wedged read can never strand the logo. The window returns to show:false — ready-to-show now reveals the first painted frame, which is the launch surface itself, and restoring maximized state defers to markReady in every reveal mode. Generated-by: Devin * fix(desktop): drop stale runtime host handoff publications Locale resolution makes each publication asynchronous, so a payload computed for a superseded or closed handoff could land after the newer state and resurrect the attention modal. Fence each publish with a monotonic ticket invalidated by update and close. Generated-by: Devin * fix(desktop): stop deferring onboarding errors once the host settles The identity-unavailable deferral kept the snapshot pending forever when the default Host never acquired an identity, hiding the composer indefinitely. Gate the deferral on the authoritative profile readiness: only 'connecting'/'reconnecting' means still pending; 'unavailable', 'disabled' or a missing entry now surfaces the error so the shell exits its loading state. Generated-by: Devin * fix(desktop): anchor the boot barrier on the first painted frame firstWindowConstructed could resolve at launch-settle before the first frame was composited, letting the Runtime Host module graph contend with the paint it was meant to follow. Resolve on ready-to-show instead — it fires at the first frame for every reveal mode, including hidden runs that never emit 'show'; launch-settle stays as the fallback resolver. Generated-by: Devin * fix(cli): reject operators that predate capability reporting The status probe dropped its capabilityRequest when the legacy lease path was removed, so a pre-lifetime-lock operator now passes the probe and gets retired without the advisory lease it needs. Restore the request and refuse operators that cannot echo it, so the failure is an explicit unsupported-operator error instead of a lock-free retirement. Generated-by: Devin * fix(desktop): poke open WorkBoard panels once their IPC registers workBoard:list rejects with "No handler registered" while the Local Host is still connecting, and the panel's error state never self-heals because nothing re-triggers the load. Emit workBoard:changed after registration so a mounted panel reloads itself. Generated-by: Devin * fix(desktop): route launch-surface dismissal through bootstrap subscriptions The hooks gate counts call sites in the shell body: the standalone useEffect pushed AppShellContent past its inventory. Move the dismissal into useAppShellBootstrapSubscriptions where the other bootstrap reactions already live, and record the useLayoutEffect shrink from the prefetch-era seeding removal. Generated-by: Devin * fix(desktop,cli): close residual host-readiness and repair-path gaps - Onboarding: subscribe Host profile changes as a snapshot invalidation, so a deferred pending re-pulls when the default Host settles unavailable instead of pinning the skeleton forever. - Update probe: emit the unsupported-operator refusal directly instead of throwing through the outer catch — the catch degraded it to "operator unavailable" under repair mode (defeating the capability guard) and the exactTargetObserved remap flattened it to a generic update_incomplete. Generated-by: Devin * chore(desktop): add missing ASF header to boot-context Generated-by: Devin * fix(desktop): let installUpdate proceed while the local Host is still starting Deferring Host boot behind first paint made installUpdate reachable during startup: retireOwnedLocalHost's 5s admission deadline expires mid-launch and the retire throws, surfacing as install_failed in the Windows autoupdate check. quitAndInstall follows immediately and the launch-owner guard closes a half-started Host on exit, so an unquiesced Host must not fail the install — same degrade the quit path already applies. Generated-by: Devin * fix(desktop): gate renderer IPC on the boot registration pass First paint now precedes the Runtime Host module graph, so invokes fired during renderer startup hit "No handler registered" instead of waiting for their handler. Park every preload invoke behind app:bootstrapReady — which main resolves once runtime-host-boot's top-level registration pass has run — and defer the module-level browser:document-ready send the same way, since a send dropped before its ipcMain.on exists never retries. The gate fails open so a call's outcome is never altered, covers every invoke site and future channels without enumerating them, and keeps window:notifyRendererReady plus the diagnostics channels immediate. The launch overlay was owned by AppShellContent, so the WorkHub surface never dismissed it; drop launch-surface.ts and the onboarding-derived launchSurfaceReady gate in favor of CSS-only dismissal keyed on #root's first child, which fires on either surface with no renderer debt. Host identity unavailability while the default Host is still connecting now pends in preload's activeRuntimeHostRef on runtime-host-profiles:changed instead of round-tripping a deferral through the renderer, so the onboarding hook no longer needs the profiles bridge. Generated-by: Devin * chore(desktop): regenerate astryx surface inventory for the handoff overlay Generated-by: Devin * fix(desktop): drop redundant shell-settings refresh on Host ready The preload identity deferral turns settings.get() into a pending read while the default Host is connecting, so the ready-transition re-pull no longer has a stale partial state to repair — and the legacy effects file must stay at its base token budget. Generated-by: Devin * chore(desktop): drop dead barrel re-exports for handoff types Generated-by: Devin * test(desktop): surface app consoles on e2e failure Attaches captured main/renderer console output to failed specs so CI failures carry the evidence instead of needing a local repro. Generated-by: Devin * fix(desktop): keep revision send alive past a slow transcript open prepareRevisionSend read the revised Session's transcript with a 480ms budget that also covers transcripts.open, which waits on Host admission and legitimately takes far longer under load. When it lapsed, the catch ran rollback — whose navigation bumps the selection revision — and only then checked selectionIsCurrent, which was therefore always stale, so the failure was discarded without a toast and the send vanished. A transcript replica that is still opening must not gate the send: the Session view fills from its own consumer and the write boundary is Host admission. tolerateOpenTimeout reports such an open as unsettled instead of failing; real open errors and caller aborts still propagate. Failure feedback now runs before rollback so rollback's own navigation cannot invalidate it. Generated-by: Devin * fix(desktop): land e2e fixture workbar writes after Session activation The Workbar keys collapse state per Session and withRightCollapsed drops any write issued while its reducer has not activated a Session yet. applyE2eFixture ran setActiveId and immediately dispatched the collapse and openTool writes, which the reducer processed before the render-phase activate-session landed — under UI-first startup the fixture is now the first activation, so both writes were eaten and the audited surfaces (.maka-session-workbar, .maka-browser-panel) stayed collapsed forever. Set the selection before awaiting refreshSessions: the IPC round trip lets React commit the activation, and a direct setWorkbarCollapsed replaces the stale rightCollapsed snapshot + toggle, which could flip the wrong direction when read late. Generated-by: Devin * fix(desktop): register the Host-change waiter before probing readiness activeRuntimeHostRef awaited runtime-host-profiles:getSnapshot before installing its profiles:changed waiter, so a transition push landing inside the probe's round trip fired an empty waiter set and the read parked forever. Install the waiter first and cancel it when the probe shows the Host settled. Generated-by: Devin * fix(desktop): activate the e2e fixture Session only once catalog-observed A runtime-host-profiles change runs a retire sweep that drops the active Session when the committed catalog lacks it. With the Host still starting, that sweep can land before the seeded fixture row reaches the catalog, retiring the Session the fixture just activated and collapsing the workbar the alignment audit waits on. Gate activation on catalog membership — the same constraint a real selection has. Generated-by: Devin * refactor(desktop): move the fixture catalog wait into session-catalog-state The committed fixture carries the wait inline plus a structural dep type, which pushes app-shell-e2e-fixture past its legacyAppShell token budget (750 vs 637). Export waitForCatalogSession from the contracts module (which the ratchet does not price), collapse the workbarTab union check and the sidebarSection if-chain into a lookup, and the file lands at 623. Generated-by: Devin * fix(desktop): drop the redundant transcript read from revision sends prepareRevisionSend opened a second transcript consumer to verify the replica before allowing the send. The Session view's own consumer already loads history, and the write boundary is Host admission — so the read existed only to span the window until commitTranscript made the new Session the send target. With the Host reconnecting that open parks on the router's candidate wait (~15s), and the tolerateOpenTimeout escape still could not settle: cancelOpen is a noop until the open delivers its close handle, and closing it never rejects the parked invoke, so the deadline waited the full router timeout anyway (review 5263634692). The send now takes the target explicitly via SendOptions.targetSessionId (from the prepared draft), so neither the read nor the commit barrier is needed: the read, the flag, and the preparation AbortController plumbing all go away. onFollowLatest is skipped for an explicit target — a fresh Session has no reading position to prepare — and isSurfaceVisible keeps gating feedback on the committed view. The revision tests assert the preparation never opens another transcript consumer. Generated-by: Devin * chore(desktop): refresh the astryx surface inventory session-catalog-state gained the waitForCatalogSession export. Generated-by: Devin --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(desktop): settle queue admissions before editing in side-chat spec The spec pressed Enter on the third follow-up and clicked edit on the first within ~100ms. beginEdit captures the queue revision at click time, so when the third entry's Host admission landed inside that window the update carried a stale expectedQueueRevision and was correctly rejected with operation_conflict. The failed commit leaves the edit textarea open, which replaces the row's queue-text span — the queue never reordered; the first entry was simply hidden behind its own edit box while the error toast reported the conflict. Wait for the third entry's edit button to become enabled — enabled marks Host-owned 'queued' state — before opening the edit, matching the spec's own note that optimistic appearance does not settle send admission. Generated-by: Devin * fix(desktop): own the native menu through its popup lifetime Two CI runs died mid-assertion after closePopup(): the main window's CDP session closed because the native menu teardown crashed the process. popupNativeMenu never retained the Menu it built, so a JS wrapper collected while its popup is open can crash the native close path (electron#20737 family). Hold each open menu until its popup callback reports it closed. The spec also closed the popup unconditionally. On Linux a popup can auto-dismiss after window resizes — this spec resizes three times first — and closing an already-dead popup hits the same teardown crash. aria-expanded tracks the popup IPC resolution, so only call closePopup while it still reports the menu open. Renderer crashes previously left no artifact evidence; the fixture now logs every page crash, including the restarted window's, into the error context. Generated-by: Devin * test(desktop): move queue row semantics to component tests The side-chat E2E asserted ComposerMessageQueue contracts through a real Electron window: the revision captured at edit click, a rejected stale-revision update leaving the row in edit mode (the misread that hid the edited row behind its own textarea), and drag reorder passing the Host-owned id list. All three are renderer-owned and now run in packages/ui against the real component with stubbed callbacks. Mutation-checked: closing edit mode unconditionally on a rejected update fails the stale-revision test. Generated-by: Devin * test(desktop): migrate skill-draft lifecycle coverage to action seams The deleted E2E asserted renderer-owned contracts: a refused send keeps the draft, a retry reuses the already-prepared child instead of forking another revision, and cancel restores the pre-edit draft text (Skill token included). They now run deterministically against createAppShellRevisionActions with a stubbed bridge, plus the blocked- Skill toast through createAppShellChatActions. The transcript-settlement gate that once raced the deferred React handoff was removed upstream in apache#5494, which already covers it with a no-second-transcript-open test. The success-path draft cleanup stays inline in sendWithAttachments — untested glue, same as before — rather than earning a new seam here. Mutation-checked: removing the retry early-return and restoring the wrong draft text each fail their test. Generated-by: Devin
The streaming label animated background-position linearly, which Blink cannot composite — every frame repainted the glyph-clipped gradient for the whole turn (~36% tree CPU measured on one label). steps(60) keeps the same sweep at ~15fps for ~9%. Cuts from auditing the same surface: the ejected ChatReasoning drops props Maka never passes (duration, controlled expansion, theme reflections nothing selects) and the eight shimmer atoms collapse into one semantic .maka-reasoning-shimmer class; data-deep-thinking had no readers. Generated-by: Devin Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…pache#5566) * fix(desktop): stop cached transcript previews offering earlier history transcripts.open replays the locally cached tail before the live history answer so the previous content is visible while the Host reads. That snapshot carries the replica's tail bound as hasOlder, so the view rendered a load-earlier control for a generation that cannot serve the read (loadEarlier no-ops on cached:), then replaced it wholesale once the live answer arrived — the "load earlier history" flash on every session switch. A cached generation is provisional by contract and every live-only affordance already gates on it. Close the two leaks: range() reports hasOlder only for generations that can answer earlier reads, and the reading-position restore waits for the live answer instead of concluding a bookmarked Turn is unreachable from the cached tail. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Generated-by: Devin * fix(desktop): publish session transcripts once, behind a fade swap Switching sessions used to paint the locally cached tail first — a prefix-truncated copy whose hasOlder flag rendered a dead load-earlier control — then replace it wholesale when the live answer arrived. The cache now stands in only when the live open fails, which also removes a per-open session-local disk read. The switch keeps the previous transcript inert and dimmed while the live read is in flight, then fades the new transcript in on its keyed remount. Generated-by: Devin --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…he#5561) The process disclosure's ::details-content animation grid had an implicit auto column track, which grows to the item's max-content: one unbreakable descendant inside .maka-processing-body widened the body past the 800px reading measure while the details' contain:paint boundary clipped every sibling at the column edge — the uniform mid-column text clipping seen in the release transcript. Pin the column to minmax(0, 1fr), give the sequence the 4px inline padding where the tool rows' intentional overhang lands, narrow the body's clip to the block axis the wipe needs, refresh the stale ProcessingBlock comment, and assert the contract in the disclosure story. Also drops dead transcript column declarations proven removable by live CSSOM ablation.
…pache#5534) * fix(runtime): make subagent spawn selection explicit and recoverable Expose callable catalog arguments, ignore inactive selector fields only in explicit modes, and provide actionable errors without changing legacy routing or parallel scheduling. Generated-by: Codex * fix(runtime): align missing selector guidance with spawn mode Report the selected identity field when an explicit spawn mode lacks its selector, retaining legacy guidance for callers without a mode. Cover recovery with and without an inactive selector present. Generated-by: Codex * fix(runtime): tighten subagent recovery guidance
Generated-by: Codex
AppShell owned context compaction: the running-notice presentation, the /compact bridge call and the hand-off of the Host's terminal outcome. Move all three into the Conversation owner. The controller creates the one presentation, compactSession calls the injected sessions.compact service, and ConversationLifecycle hands the outcome to the same presentation. AppShell keeps only the stable compactSession command until composer submission moves. The workspace-unavailable classifier moves to application/contracts. Refs apache#4582 Generated-by: Claude Code
* fix(desktop): default Workbar entry to the titlebar Generated-by: OpenAI Codex * test(desktop): cover Workbar entry modes in native WorkHub Generated-by: OpenAI Codex
* feat(desktop): fit segmented switches to their content A segmented control that spans its whole container became the heaviest object around it. The Session rail's 按时间 / 按项目 switch outweighed 新任务 and read as one more nav row, and the import/export settings page stacked two full-width bars under its title. Give each switch content width and put it at the trailing end of the heading for the area it governs: - Session rail: a 任务 heading row with the grouping switch on its right, still in the sticky top region so it never scrolls away. - 导入/导出任务: the source switch moves into the 来源 section header's action slot; the import/export mode switch keeps its place but hugs its segments. - 远程接入 (quick-onboarding providers): the 快捷接入 / 手动配置 switch moves into the 接入方式 section header's action slot at size sm. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(desktop): regenerate the Astryx surface inventory session-list-panel.tsx now imports Text for the grouping heading. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…nd retract (apache#5910) * feat(cli): bind Tab and Shift+Left for TUI queue and retract The TUI's queue (Alt+Enter) and retract (Alt+Up) chords are intercepted by Windows Terminal and never reach the app on macOS Terminal/iTerm, leaving both actions unavailable there. Add the macOS/Windows-safe pair Codex ships for the same actions: Tab queues a followup while a turn runs (only with a non-empty draft and no completion popup open, so the editor keeps owning Tab for completion and idle input), and Shift+Left retracts queued messages back into the editor. The Alt chords stay as aliases, and /help plus the pending-bar hint name both routes in all three locales. Refs apache#3538 Generated-by: Maka * refactor(cli): drop the Alt queue/retract chords and fix review failures Follow-up to the first commit: the Alt chords conflict on macOS and Windows terminals rather than merely being awkward, so they are removed instead of kept as aliases. Tab (queue, during a turn) and Shift+Left (retract) are now the only bindings. Queue retraction needs no separate key — queue.retract returns both steering and followup queues. Also fixes the two CI failures called out in review: the cross-locale keybinding-token invariant (tokens are now 'Tab' and 'Shift+Left' in all locales) and the pending-queue platform-render test (no alt token left to rewrite, so darwin and linux render identically). Refs apache#3538 Generated-by: Maka
…5904) When the Runtime Host's resume planner confirms the latest interrupted Turn can resume, the composer replaces Send with Resume while the draft is empty; typing or staging context brings Send back. Availability comes from the authoritative read-only turn.resume.query preview, surfaced through a new sessions:queryResumeLatest IPC and tracked per session in useShellResume, so the offer stays fail-closed: the click still re-runs the full safe-boundary admission and can only ever park. Generated-by: Maka (k3-256k)
…5794) (apache#5847) A1 promises lossless layout refactors, but nothing in the workflow shows whether a change moved pixels. This adds the local verification loop: one script shoots the chat-surface story set in light and dark with a fixed clock and disabled animations, and compare reports changed pixels per story with diff images. Deliberately not a CI gate and no baseline images — captures are local artifacts for the refactor's PR evidence. Generated-by: Devin Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated-by: Devin
…euse (apache#5931) The compactHistory fast path returned already_compacted on a raw-prefix match alone, while the pre-send gate rejects the same checkpoint with effective_history_changed. Fold the covered span through the current projection transitions and require the pinned effectiveSourceDigest to still match; on drift, fall through to the planner's roll-forward gate, which discards the stale checkpoint and re-summarizes the current effective view (apache#5929). Generated-by: Devin Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…n-footer resume button (apache#5927) The review-round suppression on apache#5904 hid the send-slot Resume offer for a Turn the user just stopped and relegated resume to a small ghost button in the stopped Turn's footer. That kills the primary use case: stopping a Turn mid-reply and then deciding to continue it is exactly when the user looks at the send slot, and the footer button was never covered by a test or story. Restore the original apache#5903 behavior: after a manual Stop, with an empty draft, the composer send slot offers Resume like any other resumable interruption. The accidental-restart hazard the suppression guarded against is narrow now: the slot only renders Resume after a fresh ready answer from the planner, which requires the stop's terminal transition to have completed, so a click during the settle window lands on a disabled Send; a resumed Turn can simply be stopped again. The tracker keeps the unconditional wins from the review rounds: stale-offer retraction while a re-read is in flight, and refresh on Host rebound. Fixes apache#5923 Generated-by: Maka (k3-256k)
…ache#5934) * chore(desktop): check R2 root symbol uses and retained root hooks The renderer architecture checker gains the rules apache#4582 needs to close R2 by numbers. rootSymbolUses (M0): a generated record, per feature public entry, of the runtime symbols each root zone takes from it: appShell (the AppShell family), composition, and bootstrap (bootstrap/ plus the guarded main.tsx and app.tsx). Uses are attributed through named and default imports, static namespace members including JSX members, and re-exports followed through any module until a feature public entry. Namespace escapes, wildcard or namespace re-exports over an entry, and runtime import() or require of an entry are violations. Against the base the record may only shrink, except for an export the same change adds to the entry, measured on the materialized base tree, or a use moving one way out of appShell into composition or bootstrap. The CLI lists each admitted use. Retained root hooks (M5): the renderer README now has one row per call site the AppShell hook gate allows, naming its consumer, owner, allowed capability and either the root reason or the removal module. The checker reads the gate's ALLOWED literal without running or editing it and fails when an entry has no row, when the row count differs from the gate count, or when a row names a hook the gate no longer lists. --report prints the M3/M5 completion measures: AppShell-family bridge references and action factories, the Conversation transitional rows, hook-gate entries without a row, and root symbol uses per zone. Seven feature exports that only tests or Storybook read move from public entries to testing.ts (or, for the WorkHub coordination lifecycle, to its application contract). The README lists the remaining non-assembly root exports outside Conversation with their consumer and removal module. Generated-by: Claude Opus 5.5 * chore(desktop): compare root symbol growth by binding and bind table rows to calls Review follow-ups on the R2 enforcement rules. Root symbol admission now compares bindings, not export names. Each public export resolves to its declaring module and local name, so re-exporting an existing binding under a new alias no longer counts as a new public export. The AppShell closure stays outside the root zones, but its feature-entry uses are now visible: --report lists them per file, and a --base run prints each one a change adds, reported and never ratcheted. Where a hook-gate entry has several call sites, each retained-root row must name an identifier of exactly one of those calls in app-shell.tsx, and no two rows may name the same call. The two useEffect rows now name setWorkHubEnabled and defaultHostConnections. Generated-by: Claude Opus 5.5
…ssion below the shell (apache#5935) * refactor(desktop): own task readiness below the Composer Move the task-readiness snapshot, its refresh revision and request fence from AppShell into a persistent TaskReadinessProvider in Conversation, mounted beside ComposerStagingProvider. The two Host reads reach the feature through an injected TaskReadinessServices port and a Desktop adapter. The transcript surface reads the notice through TaskReadinessNoticeConsumer instead of receiving it as props. AppShell now passes only the request projection, targets, refresh key and workspace-picker command. The legacy use-task-submission-readiness hook and the task-readiness-notice re-export are retired, and the public entry no longer exports the notice derivation. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): let Session Settings own new-task mode choices The new chat's Plan toggle, orchestration value and permission choice were AppShell state (two useState calls and useNewTaskChoice). SessionSettingsProvider already decided between the selected Session and the new task for permission writes, so it now holds all three. The shell reads them through the existing useSessionSettingIntent hook and writes them through setNewTaskPlanMode, setNewTaskOrchestrationMode and clearNewTaskPermissionChoice. useNewTaskChoice moves to application contracts, because Session Settings and the Conversation chat-model hook both use it. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): own Composer submission below the shell Move the send-pending flag, the edit-and-resend draft and the submit, follow-up and interaction-answer paths out of AppShellContent into a persistent ComposerSubmissionProvider in Conversation. The provider assembles the chat and revision actions, the staged follow-up and createRevisionAwareOnSend. ConversationComposerRegion reads the provider in the Composer slot. The shell keeps one stable command handle, beginEditUserMessage, and supplies only commands it already owns: navigation, catalog refresh, Workbar side chat and form answers, and the selected Session's orchestration write. The Host calls (submitMessage, newTasks.create, sessions.remove, reviseBeforeTurn, abandonSessionCopy, and the sandbox-boundary and question answers) go through ComposerSubmissionServices and a Desktop adapter. Shared helpers move to application contracts or into the feature. The submit construction and unused exports leave the public entry, and the transitional adapter loses the commands only the moved actions used. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): move Stop, Turn branch and local recovery under the Composer owner The Stop and Turn-branch actions move from AppShell-family legacy files into ComposerSubmissionProvider. Their Host calls, sessions.stop and branchFromTurn, now go through ComposerSubmissionServices. The Composer slot reads the published Session's Stop claim and receives onStop/stop from the owner; Stop still notes the stopped Turn for the resume offer. The shell's command handle gains handleTurnFooterAction and receives the Turn-action pending registry as a port, because the shell still renders that registry's mask. SessionLocalMessages, the local delivery-recovery reader, now mounts inside the owner for the published Session. Its recovery policy is unchanged. The transitional adapter drops the Stop claim, the transient add/remove commands and captureSelection, and the README's matching transitional row is removed. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): address apache#5935 review follow-ups - Move the missing-working-directory toast into application/contracts/session-workspace-errors.ts. Contracts cannot import copy catalogs, so callers pass the copy. The Conversation copy is deleted, and the chat, revision and Turn actions call the contract. - Pin the steering Turn id: an owner test sends with followUpMode "steer" while a Host Turn runs and checks that the pending row carries that Turn's id before the Host answers. - TaskReadinessProvider takes the recovery Session id and the stable recovery and Add Project commands instead of a closure built on every shell render, and memoizes the picker action. A shell render with the same facts no longer republishes the notice context. Refs apache#4582 Generated-by: Claude Opus 5.5
…pache#5936) * refactor(desktop): read transcript attachment bytes through the Conversation port AppShell passed window.maka.attachments.readBytes to the transcript as a prop. Declare readBytes on Conversation's attachment port (ComposerStagingServices), whose Desktop adapter already returns the bridge's attachments namespace, and have StagedQuoteChatView, the feature's transcript ChatView, take the reader from it. The reader contracts of ChatMessageSurface and StagedQuoteChatView drop the prop, so AppShell can no longer supply one; app-shell.tsx goes from five direct bridge paths to four. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): own WorkHub enablement in an application authority AppShell read the client WorkHub switch from the Desktop bridge into its own state and passed it to Workbar, the session rail and the WorkHub dock. Create a WorkHub enablement authority in application/contracts beside the WorkHub workspace contract, with its Desktop source injected at composition like the session catalog. Workbar, the rail and the dock read it directly; the rail and WorkHub's main-window navigation check it again before opening WorkHub. WorkHubEnablementWatch hands AppShell only the on/off edges for the navigation it still owns (workHubActive and the destination). AppShell loses settings.getClient, settings.subscribeClientChanged, the workHubEnabled state, its ref and its effect. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): own onboarding in an application authority AppShell owned the onboarding snapshot: the poller, its invalidation subscriptions, the refresh after Settings closes and the bridge write behind "skip setup". Move the poller into an onboarding authority in application/contracts, created at composition with a Desktop source (the former onboarding-snapshot-bridge, now also writing the skip milestone on the default Host). The session rail reads per-Session send outcomes from it directly. AppShell receives a read-only projection plus refresh and skip through OnboardingProjectionRoot, the same render-prop pattern as the other shell roots, and derives first-run gating, the default-Host connection seed and the activation candidate from it. A failed read is now a flag. The localized error text was never shown, so nothing that might carry paths or tokens is kept. The snapshot types move to src/shared so the application layer can name them; preload re-exports them unchanged. AppShell loses onboarding.setMilestone, useOnboardingSnapshot and the sessionSendOutcomes prop it threaded to the rail. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): give the session catalog its Desktop source The shell's catalog refresh called window.maka.sessions.list itself. The session catalog now takes a SessionCatalogSource (full lists and the change feed), injected at composition from the Desktop session catalog adapter, and the shell refreshes through catalog.source. A catalog built without a source, as in tests and stories, is detached. use-app-shell-session-list.ts loses its sessions.list bridge path. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): move the root lifecycle's Desktop events behind injected sources app-shell-effects.ts subscribed to seven Desktop bridge paths directly: app.info for the document's platform tag, the window menu, connection events, Host profile changes, Session changes and both settings-change feeds. The reactions stay a root application lifecycle, since they refresh several regions at once, but the environment leaves the shell: - ShellLifecycleSources (application/contracts/shell-lifecycle.ts) is supplied at composition by a Desktop adapter that also writes the data-os tag. - ShellLifecycleSubscriptions is the one subscriber. It takes Session changes from the session catalog's own source. - useAppShellBootstrapSubscriptions keeps the startup refreshes, the hotkeys and the mounted flag, and returns the handlers. Every handler now reads the latest render. The two settings-change handlers used to capture the first render's connection refresh; they now refresh the current connection projections, as the Host-change handler already did. app-shell-effects.ts loses all seven bridge paths and one effect. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): read turn landmarks through Conversation services AppShell handed ConversationLifecycle an inline window.maka.sessions.listTurnLandmarks reader. Declare listTurnLandmarks on ConversationServices.sessions; the Desktop adapter already spreads the bridge's sessions namespace, so it supplies the function unchanged. The lifecycle passes it to the reading-position controller, and its prop is gone, so AppShell can no longer supply a reader. The controller never listed the reader as an effect dependency, so its identity becoming stable changes nothing. app-shell.tsx loses its last direct bridge path. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): require the root authorities' providers and test the lifecycle handlers Review follow-up for apache#5936: - The onboarding, WorkHub-enablement and shell-lifecycle contexts throw when their provider is missing, as createServicesContext does, instead of falling back to inert values that would, for example, hold the first-run gate closed. A catalog built without a source still accepts commits but fails when read through that source. Tests pass explicit fakes. - The event-to-reaction mapping moves into the pure createShellLifecycleHandlers beside ShellLifecycleHandlers, so it is unit-tested without loading app-shell-effects.ts. - AppShell imports WorkHubEnablementWatch from its contract; the WorkHub feature entry no longer re-exports it. Refs apache#4582 Generated-by: Claude Opus 5.5
…ktop calls to their owners (apache#5937) * refactor(desktop): route legacy diagnostics reports through the diagnostics feature The Error Boundary, About and the command palette still called window.maka.diagnostics.copyReport from legacy renderer files after apache#5892 moved the root's diagnostics into features/diagnostics. Each now receives one fixed-surface command from the feature's services instead: - the Error Boundary takes copyRendererCrashReport from an optional RendererCrashReportConsumer; without Desktop composition it still renders its fallback and copies its own browser report; - About takes copyManualReport from ManualDiagnosticReportConsumer; - AppShell takes the same command through that consumer and passes it to the palette in its command options. The Desktop adapter fixes the manual and renderer_crash surfaces and forwards the same fields as before. DiagnosticReportToastProvider now reads its action's words from the shared shell catalog itself, so AppShell no longer reads shell copy to hand them over. No renderer file outside platform/desktop references the diagnostics bridge any more; the ledger drops six bridge paths, and re-adding any of them is new bridgePaths debt. Refs apache#4582 Generated-by: Claude Code * refactor(desktop): retire the AppShell project actions into Task Entry createAppShellProjectActions built twelve project actions for AppShell, but nine of them (add, select, select-no-project, prepare, prepare-default, relink, rename, archive, restore) had no production caller: the rail and the new-task flows have gone through taskEntry.commands since apache#5527. Delete them with their helpers, the project-picker state and refs that only addProject used, and the onProjectSelected callback that only those actions fired. The two live actions move to Task Entry, whose README already names it the Task Entry / Workspace owner: - TaskEntryServices gains folders.openProjectFolder(sessionId?) and openWorkspaceFolder(). The Desktop adapter opens a task's folder through the task and anything else on the default Runtime Host, and names the task or Host profile a refusal or failure belongs to. - The controller exposes openProjectFolder / openWorkspaceFolder commands and reports failures in the same shell copy, targets and workspace-unavailable notice as before. TaskEntryError may now carry a sessionId instead of a profileId. - AppShell hands taskEntry.commands to the titlebar and the palette. refreshProjects stays with the workspace projection in use-project-context.ts, inlined on runOnDefaultRuntimeHost. app-shell-project-actions.ts, its workspace-projection ownership entry and the now unused openPathActionErrorMessage are removed. Refs apache#4582 Generated-by: Claude Code * refactor(desktop): give the command palette's Desktop actions an overlays port Five command-palette rows still called the Desktop bridge from app-shell-command-actions.ts: testing a connection, making it the default, testing the network proxy, opening the local memory file and saving the conversation to a file. They now reach Desktop through the palette owner, features/overlays: - OverlaysServices gains a palette port (OverlayPaletteActions) whose Desktop adapter makes the same bridge calls with the same arguments, including the slug form of connections.test/setDefault, which is a different IPC channel from Connection Settings' identity form. - The overlays projection hands the port to AppShell as paletteActions, and AppShell passes it in the command options. The rows keep their default-Host resolution, toasts and failure copy. app-shell-command-actions.ts no longer references window.maka; together with the diagnostics report, its six bridge paths are gone. The overlays entry-surface pin now lists OverlayPaletteActions for that file. Refs apache#4582 Generated-by: Claude Code * refactor(desktop): share the open-path copy helpers and keep the titlebar opener stable Review follow-ups for apache#5937: - openPathFailureCopy and openPathActionLabel move into the shell copy catalog beside the strings they read. Task Entry's folderOpenFailure now calls them instead of its own copy of the lookup, and open-path.ts re-exports them for Settings and the artifact pane. An application contract cannot hold them: the application zone may not import copy catalogs. The renderer's unused OpenPathKey / OpenPathFailureReason aliases go with the move. - AppShell's openProjectFolder is a useCallback over taskEntry.commands and the owner session, so the titlebar no longer receives a new onOpenFolder on every commit. Refs apache#4582 Generated-by: Claude Code
* feat(acp): add mode selection and scoped catalog lifecycle Generated-by: Codex * fix(acp): preserve executor configuration authority Generated-by: Codex * fix(protocol): validate optional executor catalog fields Generated-by: Codex * fix(acp): reconcile reviewed configuration races Generated-by: Codex * fix(acp): preserve confirmed selection across agent drift Apply merged configuration once when opening a fresh Session, so a model change may remove an obsolete mode. Generated-by: Codex * fix(acp): keep saved selection across agent drift Generated-by: Codex * docs(acp): reconcile official agent acceptance findings Generated-by: Codex * fix(desktop): select extracted ACP directory on macOS Record authenticated Desktop acceptance and post-restart continuation. Generated-by: Codex * fix(acp): validate mode after model selection Generated-by: Codex * docs(acp): record real directory refresh isolation Generated-by: Codex * docs(acp): record second project desktop catalog Generated-by: Codex * docs(acp): reconcile second project acceptance summary Generated-by: Codex * docs(acp): record desktop cross-project acceptance Generated-by: Codex * docs(acp): complete repeatable PR 5826 acceptance procedure Generated-by: Codex * docs(acp): record inconclusive eligibility recheck Generated-by: Codex * fix(acp): advance mode catalog protocol epoch beyond main Generated-by: Codex * docs(acp): record successful official execution recheck Generated-by: Codex * docs: close official Agent Desktop acceptance Generated-by: Codex * fix(acp): restore model before dependent mode on rollback Preserve the retained Session and durable selection when a model change exposes different mode candidates. Cover stale and invalid modes, explicit mode restoration, and same-Session prompt retry. Generated-by: Codex * fix(ui): block sends until executor mode is confirmed Aggregate mode and model configuration pending state into the Composer send gate. Preserve drafts until confirmation settles and release the gate when the selector unmounts. Generated-by: Codex * fix(acp): isolate draft catalog probes from project sessions Generated-by: Codex * fix(acp): validate program selection and consolidate protocol history Generated-by: Codex * chore(test): sync Windows program selection skip inventory Generated-by: Codex * fix(acp): reuse leased catalog paths and restart invalidated waiters Provide stable namespaced scratch directories through Plugin storage and reuse existing native file leases across runtime instances. Keep leases through probe cleanup and recover residue after owner exit. Redirect catalog waiters by revision without retrying genuine failures. Generated-by: Codex * docs(acp): record stable probe and preceding desktop acceptance Record official Agent checks at 4fefdc1, production Plugin bundle integration, 286 affected tests and negative regressions. Keep Desktop 1-4 and historical 5-9 evidence scoped to their tested implementations. Generated-by: Codex * test(acp): cover catalog refresh and mode configuration boundaries Generated-by: Codex * fix(desktop): preserve executor controls during first send Show Retry only when the executor catalog is unavailable or has an error. Carry the selected catalog into locally pending sessions, defer Host inspection until admission, and preserve the selected model and mode in the pending summary. Generated-by: Codex * fix(test): import executor composer through conversation API Generated-by: Codex * fix(acp): reject unavailable explicit modes before prompting Require confirmation of every explicit and saved configuration value after model selection. Preserve model-only mode side effects by keeping the inherited mode out of the Host provider request. Cover retained-session drift, explicit-mode rollback, and Host patch intent. Generated-by: Codex * fix(acp): recover stale modes and restore catalog refresh
…nds (apache#5951) `e2e/session-workbar.spec.ts:179` ("Terminal survives navigation and reload…") is flaky on main. It always fails at line 224: after `page.reload()`, the owner Session's right Workbar comes back collapsed, so the terminal region never appears. This is a product bug, not a harness problem. A reload can drop every other Session's per-Session Workbar visibility. The cause is a race at startup. After a reload, a `sessions:changed` event from a Session whose turn is still finishing (the test does not wait for the replacement Session's turn to end) is read by the patch drain. `catalog.commitPatch` commits it before `bootstrapSessions()`'s `sessions.list()` does. `commitPatch` moves the catalog `revision` off 0, and `selectAuthoritativeSessionIds` read any `revision > 0` as a membership observation. It therefore published a one-row set. `useWorkbarLayoutState` dispatched `retain-sessions` with it. No Session is active yet at that point, so every other Session's `collapsedBySession` entry was dropped, and the right-visibility effect persisted the loss to `maka-session-workbar-collapsed-v2`. The rate went from 9/40 at 8ad836c to 19/40 at 255ae23 (Fisher p = 0.034) but the root cause is older. Row patches have been able to land before the list since apache#5532. The range 8ad836c..255ae23 changes nothing on the catalog, Workbar layout, main, preload or runtime path, so apache#5934/apache#5935 only moved the timing. Generated-by: Claude Opus 5.5
After apache#5934–apache#5937, `npm run check:renderer-architecture -- --report` still listed two retained-root rows scheduled for M5. M5's fourth item, "remove migrated legacy exceptions, public raw-state exports and redundant props/helpers", and its fifth, "document every retained root projection/lifecycle with its actual consumer, owner and allowed capability", were also open. This PR closes all three, in five commits: | Commit | What changes | Who loses what | |---|---|---| | `61cad0bcc` | `OnboardingConnectionSeed`, a render-null watch beside the onboarding authority, seeds the default Host's connection projection from each accepted snapshot, or asks it to refresh when onboarding cannot be read. | `AppShellContent` loses its last `useEffect` and the M5 row for it. | | `e485fa592` | `useAppShellProjectContext` stops returning the default Host's project list, the Local Host's projects and the raw selected id. It also drops the Local Host project subscription (`projects.getLocalSnapshot`, `subscribeLocalChanges`), which nothing has read since apache#5937 moved project mutations to Task Entry. Its row moves from "M5" to "application lifecycle" (see below). | `use-project-context.ts` loses 2 bridge paths and 1 effect. | | `eae997042` | Props nobody reads are removed (details below). A type-level test pins the trimmed contracts. | AppShell stops computing 3 chat-model values and importing `ProviderLogo`. | Generated-by: Claude Opus 5.5
Fixes apache#5870 Testing an MCP connection while its detail dialog is open now shows a readable, dismissible result above the backdrop. Astryx's shared toast viewport uses a stable portal host inside the active native modal, then opens its popover; raising `z-index` or reopening a popover outside the modal leaves the notification obscured or inert. The host moves back when the dialog closes without remounting toast rows. Environments without native popover and modal-selector support retain the inline viewport. The change extends the existing Astryx dependency patch (source and distributed JavaScript), documents its removal condition, and adds browser hit-testing, keyboard-focus and dismissal assertions to the existing MCP detail story. | Before | After | | --- | --- | |  |  | [Dark-mode capture](https://raw.githubusercontent.com/liuxiaocs7/maka/6c14d18dce9e1f6b513553fb5f0f9a6b59bfbb03/after-dark.png) Generated-by: Codex
Recall reconstructs each candidate Session before ranking passages. That reconstruction issued `4N + 2` SQL reads for N runs and repeatedly decoded the same event payloads. With 20,000 Turns and ten returned passages, the query took **1,603.63 ms** and a complete message read issued **80,002 SQL reads / 160,000 JSON parses**. Add a storage batch snapshot that reads openings, events, lightweight ordinals and partial presentation in one SQLite read transaction. Reuse decoded opening/terminal events and preserve the existing projection, ordering, legacy-opening and corruption checks. Wire the capability through the production storage facades; stores without it retain the individual-reader path. Resolve corpus-count fallback before reading transcripts so unavailable counts no longer make Recall reread candidates. Fixes apache#5877 Refs apache#5368, apache#5531 and apache#4677. Generated-by: Codex
…very below the shell (R2 M3) (apache#5954) * refactor(desktop): replace Conversation's export-star lines with explicit exports The public entry re-exported six modules wholesale. Production code outside the feature uses three of their names: `useShellChatModel` and `SessionHealthNoticeView` (AppShell and the chat surfaces) and `executorComposerProps` (AppShell). Only those stay public. The names that only tests read move to `testing.ts`. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): give Copy and Save a Conversation export command Copy and Save read the published messages through the transitional adapter's `readMessages` and rendered them in the command palette. The Conversation controller now owns `renderPublishedConversation`. It renders the published range as Markdown when called, and the palette receives that export, not the messages. `conversation-markdown` moves into the feature. The adapter drops `readMessages`, and the corresponding row leaves the Conversation README's transitional table. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): replace the shell's Composer handle with named edits AppShell held the main Composer's editor handle in 16 places and passed it to Workbar and Session Collaboration. Those callers could read drafts back and write any draft key. Conversation now builds `ComposerEditingCommands` from its own handle: - appendText, replaceText, focus and openModelPicker for the visible draft; - seedDraft for Work Board and discardDraft for a Guest's settled turn request; - claimVisibleDraft for Module Hub's later append, which stays current only while the same editor is mounted. None of these reads a draft back. The shell's queue surface carries these intents and the plate's entry actions, and nothing else. `ConversationComposerRegion` attaches the handle to the Composer itself. Workbar and Session Collaboration take the two keyed intents structurally. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): move Turn marks and the resume offer into the submission owner AppShell called three hooks for the Turn footer and the resume offer: `useTurnActionRegistry` held the pending marks and lent them to the submission owner through its shell port, `useAppShellTurnPresentation` derived the footer from them, and `useShellResume` built the banner and send-slot resume actions. The submission owner now holds the marks and the resume instance. `ConversationTranscriptRegion` derives the Turn presentation from the owner's marks (Branch is still withheld from a shared Session) and injects the banner's resume action; `ConversationComposerRegion` injects the send-slot offer. One resume instance stays behind both. The shell's command handle gains `clearPendingTurnActions` for Session teardown and Host changes, a no-op while the owner is unmounted. The two resume actions are memoized, so the owner's reader keeps its identity while nothing the actions show changes. The hook gate and the retained-root table drop the three rows; the legacy registry file moves under Conversation. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): read the displayed Session's Turn in the regions that render it AppShell subscribed to the displayed Session's load, Turn summary, interaction and queue through `useAppShellSessionUiReads`, derived live-turn flags with `useShellLiveTurn`, and computed every control a running Turn holds in its own render body. Each Turn boundary therefore re-rendered the whole shell. Each region now reads what it renders: - `ConversationComposerRegion` reads the Turn summary, the queue and the owner Session's interaction. It derives Stop, the mode, permission and goal reasons, the model-switch gate, the executor picker's hold and the slash commands (`/compact` is withheld while a Turn runs). The shell passes only the catalog row's arrival and status and the executor selection. - `ConversationTranscriptRegion` injects the running Turn's activity and holds the health notice's model picker for the same Turn. The shell's `useShellChatModel` keeps the status half of that gate. - `ConversationActivityConsumer` feeds the custom pet whether an observable Turn runs and whether the owner Session waits on an answer; `petActivityForSession` maps that onto the pack state. - `ConversationHomeSurface` renders the main column and marks it as the home surface when the shell's empty-transcript condition holds and there is no live Turn content and no failed load. The gate logic moves unchanged into `composerTurnGates` and `liveTurnFlags`. The two legacy hooks, their gate entries and retained-root rows, and the Conversation README's last transitional row are gone, together with the `chatTurnActivity`, `executorComposerProps` and `desktopSlashCommandPresentation` exports. Refs apache#4582 Generated-by: Claude Opus 5.5 * fix(desktop): give a withdrawn send's staged context back to the Session it left Editing a queued steering entry or a cancelled local message restores its text to the editor's keyed draft, and was meant to restore its attachments, directory references and quotes through the queue's `draftContextRestorer` slot. apache#5747 removed the only line that filled that slot when it moved the shell's staging calls, so since then the text came back and the staged context was dropped. Staging now offers `restoreContext(draftKey, context)` on its command handle, a no-op while no owner is mounted, and the Composer submission owner binds the queue's slot to it. The restore stays keyed by the Session the send left, so it lands there even after navigation. A message whose outcome is unknown keeps its identity: it offers only the Host check, bound to its Session and Message. Refs apache#4582 Generated-by: Claude Opus 5.5 * docs(desktop): retain the last three M3 root hooks with their cross-region reasons `useActiveExecutionBoundary`, `useSessionSettingIntent` and `useShellChatModel` were the last retained-root rows scheduled for M3. Each serves several regions the root composes, so each stays at the root with the `cross-region command` reason and its full consumer list: - the execution boundary feeds the Composer's permission control and unreadable notice, the palette's permission-mode command, the Session Collaboration dialog gate and the health notice's picker gate, and is reloaded by the submission owner and Conversation lifecycle; - the setting overlay feeds the Composer's model, thinking and mode controls, the transcript's model picker, the palette's permission-mode command, new-task creation and Session teardown; - the model selection feeds the Composer's model and executor pickers, the transcript's labels and health notice, the staging vision gate, the readiness and new-task submission model and Workbar. The exports table's remaining M3 rows (the workspace picker, guest Turn requests and the skill catalog boundary) are other features' projections into the Composer; a feature cannot import another, so the root composes them and they stay. The guest row no longer mentions the Composer ref, which it lost to the named edits. Refs apache#4582 Generated-by: Claude Opus 5.5 * refactor(desktop): narrow the transcript's submission read and type its picker gate Review follow-ups on apache#5954: - The Turn-action registry clears its timers and marks when its owner unmounts. The shell's unmount call reached an already-unbound handle, so it is removed, and with it the no-argument "clear every Session" form: `clearPendingTurnActions(sessionId)` now needs a Session. - The transcript and the activity reader read a narrow Turn reader (displayed and owner Session, pending marks, the banner's resume action). A send-pending or edit-draft change no longer repaints the transcript. - The health notice's picker gate is a typed region input, `localInteractionAvailable`, instead of a prop read through a cast. Refs apache#4582 Generated-by: Claude Opus 5.5
Align the top-right settings menu to the trigger's end edge and add real-browser geometry regression coverage for narrow and regular desktop viewports. Generated-by: Maka (GPT-6.1)
…#5928) The quote chip, turn footer and lineage badges restyled Astryx Buttons through dedicated product classes that repainted chrome ghost buttons already own — or rebuilt the focus ring as an inset shadow because the chat scrollport clips the outward one. Drop the per-Button hooks and reach the same surface through the extension points the components publish: - `.maka-quote-chip .astryx-button` keeps only the product delta (supporting type, muted ink, shrink-to-clip sizing); the expanded state now reads the button's own `aria-expanded="true"`. - `.maka-turn-lineage-row` sets `--_button-radius` — the derived var Astryx's own Thumbnail/Carousel retune — and the color rules scope through `.astryx-button[data-direction]`. - `.maka-turn-footer` and `.maka-message-meta` set `--focus-outline-offset` so the component's own outline draws inside the border box instead of a replacement inset shadow. `markerVariants` drops its `lineage-badge` and `footer-action` variants; the remaining ones only ever mark product composition. Adds a TurnView lineage story so the badges' resting chrome and the retuned footer ring get pixel coverage; its play holds focus on the footer branch action, so the retuned ring is in the frame. Before/after run on the new base (42 shots, light+dark — the 20-story chat surface set plus regenerated-conversation's lineage badges): all identical. The one intentional visual delta remains the focused footer ring — inset box-shadow in --focus-ring → native outline in --focus-outline-color (= --accent-solid, the contrast-clearing tier) — pinned by the story's computed-style assertions. Refs apache#5793 Generated-by: Devin (devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ion (apache#5866) Fixes apache#5865 Setting a custom relay with no enabled chat model as default currently produces a generic error. The action now guides the user through choosing a model: - Switch directly when one chat model is enabled; prompt when several are enabled. - When none are enabled, explicitly select a model and confirm **Enable and set as default**. An empty inventory offers discovery or manual entry without silently enabling the first result. - Enable the selected model and change the default in one catalog commit. Cancellation or a rejected commit retains the original default, with actionable errors for stale or unavailable selections. Production components/styles rendered in Chromium with a synthetic connection and stubbed bridge: | Before | After | | --- | --- | |  |  | <details> <summary>Empty model inventory</summary>  </details> Generated-by: Codex
Preserve the current persistence, Recall, plugin, and transcript event wiring when integrating the execution composition split with main. Keep graph coordinator drain running when supervisor wake drain fails, and cover persistence ownership, lifecycle ordering, and subscription cleanup. Generated-by: OpenAI Codex
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Combined review of head 954442bb from two independent review passes (behaviour preservation and adversarial breakage), synthesized by the review lead. Verdict: COMMENT, no P0–P2, P3s only.
Status. Against current main (merge-base 3597abe8) the real change is 11 files, +4347/-3598; GitHub's larger figure comes from the merge commits. test CI passes, the PR merges cleanly, and no runtime-host protocol files change, so no epoch claim is needed.
Behaviour. Every piece moved out of the original execution-composition.ts (sandbox/workspace filesystem adapter, tools, agent-graph construction, history readers and subscriptions) is logically identical to the original. Construction order, agent-graph recovery, drain order and close order (supervisor wake → client → coordinator → store) are preserved. All old exports are re-exported, so every importer in packages/ still resolves, including the lazy-import test. There are no new import cycles and no services constructed twice. Locally, runtime-host typecheck and build pass, the new tests plus execution-composition pass 55/55, and nearby suites pass 151/152. The one failure also fails on main here because bwrap isn't installed.
P3
execution-composition.ts:37: "Fixes #3911" is met only by moving the 3,380-line body intoexecution-domain-composition.ts; about 590 lines went into new modules, and agent-graph construction is still in the big file. Consider "Refs #3911". Keeping the old file as a pass-through also hides the rename from git, so the diff reads as a near-total rewrite. Renaming in place, or splitting into a pure-move PR and an extraction PR, would make this much easier to review.execution-domain-composition.ts:3122: startup-failure cleanup now also closes and awaits the supervisor wake and coordinator. It's safe, arguably a fix, but not disclosed under "no behaviour change", and no test drives a startup failure through the new modules.agent-graph-composition.ts:92: multiple drain/close failures now arrive as a nested aggregate error instead of separate entries.tool-composition.ts:130:readMessagesis no longerasyncand resolves the session manager before the abort check.tool-composition.ts:196:childHostToolsis returned but unused.- Stale docs (not in the diff):
docs/architecture/runtime-host-architecture.md:133and its.zh-CN.mdtwin,packages/runtime/README.md:43, and the comment atpackages/core/src/session.ts:362still point toexecution-composition.ts.
…ntics Restore abort-first asynchronous Recall history reads and expose individual Agent Graph lifecycle hooks to preserve error ordering without added nesting. Cover late startup cleanup and history listener release, remove the unused returned child tool catalog, and clarify composition implementation links.
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental review of head 293e8445 against our previous review of 954442bb. Verdict: COMMENT. There are no P0–P2 findings, and two P3s remain, both about scope and disclosure.
Status. Main has not moved: both heads share merge-base 3597abe8. The delta is the two new commits (23fb5900 preserving cancellation and cleanup semantics, and 293e8445 aligning the storage writer guard). Against main the real change is 16 files, +4566/-3603. test CI passes on 293e8445, GitHub reports the PR as mergeable (blocked only on review), and no runtime-host protocol files change, so no epoch claim is needed.
Fixed since 954442b
- Nested aggregate errors:
RuntimeHostAgentGraphCompositionnow exposesdrainHooks/closeHooksarrays, so the module runner aggregates each authority's failure flat again, as on base. A new test pins the flat six-error shape and the single attempt per hook. readMessages: it isasyncagain and takes agetSessionManagergetter that is resolved after the abort check, which restores the base ordering. Tests cover abort-before-acquire and asynchronous rejection when the getter throws.childHostToolsis no longer returned. It is still used internally to keep recall tools away from child agents, which is correct.- Stale doc pointers in the architecture docs (EN/zh-CN),
packages/runtime/README.md, and thecore/session.tscomment now nameexecution-domain-composition.ts. - Startup-failure cleanup now has a test. A late failure after all graph bindings proves that wake, client, and coordinator close in order, that startup awaits them, that close failures aggregate as
[startup, wake, coordinator], and that history listeners are detached before storage closes. - The storage writer-ownership guard now exempts
execution-domain-composition.tsinstead of the wrapper. The wrapper constructs no writers, so the guard still holds.
Still open (P3)
execution-composition.ts:37: the body still saysFixes #3911and "2,064 → 42 lines". The 3,382-line body only moved toexecution-domain-composition.ts, and the pass-through still hides the rename from git. SuggestRefs #3911.execution-domain-composition.ts:3121: the startup-failure path now closing wake and coordinator is tested, but the PR body still answers "No" to behaviour change. One line under Summary would cover it.
| export { runtimeHostFilesystemWorkerRuntime } from './sandbox-composition.js'; | ||
|
|
||
| /** Public composition root for the Runtime Host execution domain assembly. */ | ||
| export function createExecutionRuntimeHostComposition( |
There was a problem hiding this comment.
P3 (scope, still open; no runtime impact): the PR body still says Fixes #3911 and "2,064 → 42 lines". On base 3597abe8 the file is 3,617 lines, and the assembly now lives in execution-domain-composition.ts (3,382 lines), so merging would auto-close #3911 while the bottleneck remains under a new name. Suggest Refs #3911, or more extraction before claiming the fix.
| } catch (closeError) { | ||
| errors.push(closeError); | ||
| } | ||
| for (const closeGraphAuthority of agentGraph?.closeHooks ?? []) { |
There was a problem hiding this comment.
P3 (disclosure, partly addressed): thanks for the new late-startup-failure test. It pins this path well. The behaviour still differs from base, though: base closed only graphClient and graphControlStore here, and this now also closes and awaits the supervisor wake and coordinator. The PR body still ticks "No" for behaviour change. A one-line note under Summary would make that accurate.
Summary
Fixes #3911
Verification
npm --workspace @maka/runtime-host run buildnpm --workspace @maka/runtime-host run typecheckgit diff --checkpassedAI use
Select exactly one:
Tool(s) and scope: OpenAI Codex implemented the composition refactor, added focused tests, and ran the reported validation.
Checklist
Does this PR entail a change in behavior?