* feat(agents): record run-end worktree cleanup outcome
Persist removed, retained, and failed run-end cleanup outcomes on managed worktree records. Operators and QA can inspect the durable fact through worktrees.list and openclaw worktrees list --json.
Release note: Managed worktree run-end cleanup now records why a checkout was removed or retained in worktree list JSON.
* test(qa): prove dirty worktree retention outcome
* chore(protocol): regenerate swift gateway models
* fix(agents): harden worktree cleanup recovery
Register run_end_cleanup_json as a lazy compatible column so same-version v6 index repair and read-only doctor migration can recover databases created before the column existed.
Type removal contention at the registry boundary; unexpected claim failures now best-effort record a bounded failed outcome and rethrow the original error.
* fix(ci): clear repo-wide lint debt blocking merge gates
The red-main landing rule requires this PR to repair repository-wide merge-gate debt instead of bypassing it. Apply the current lint contracts mechanically and split turn-transition coverage into a concept-named sibling with per-file-safe test state.
Exact line delta: +676/-574 (net +102) across 44 test/support files.
* fix(ci): preserve cached health refresh proof
Require the public refresh call to exist before accepting that sensitive fields were omitted, so the boundary proof cannot pass on a missing call.
* fix(ci): correct test typing left by the lint sweep
Literal-widened totalTokensVersion fixtures, a WebSocket RawData overload
mismatch, and the protocol schema document cast broke check-test-types
after the repo-wide lint repair. Aligns the fixtures with SessionEntry,
narrows Buffer handling per RawData, and keeps the JSON-shaped undefined
omission under structuredClone.
* test(agents): reuse upstream resource-loader test support
The session-loop split and #120463's helper extraction landed the same
createResourceLoader/createCompactionHandlers twice; the rebase kept both,
orphaning main's agent-session-loop-resource-loader.test-support.ts and
failing the dead-code gate. Import the upstream helpers and delete the
duplicates.
* fix(agents): reject finalized rows at the worktree removal claim
Address the accepted ClawSweeper late-claim finding by rereading and rejecting missing or finalized worktree rows inside the synchronous removal-claim transaction.
Preserve the authoritative cleanup invariant: finalized contenders record nothing, while retained-busy is written only while the row remains live.
* refactor(agents): reuse registry update for busy outcomes
Keep the live-row conditional write in the canonical registry update path so the finalized-claim repair stays below the registry max-lines ratchet without weakening the authoritative-outcome invariant.
* test(agents): drop session test duplicates after rebase
Keep current main as the canonical owner of next-turn lifecycle coverage and correctness test support after replaying the older lint-debt split.
* fix(agents): guard post-abort cleanup outcomes against finalization
After abortWorktreeRemoval releases a stale remover's claim, its retained or
failed write raced a finalizing remover and could overwrite the authoritative
removed-lossless fact. Route every retained/failed write through the live-row
condition; only the finalizing remover's own removed-lossless write stays
unconditional.
* fix(agents): persist the removal outcome atomically with finalization
A delayed removed-lossless write after remove() finalized could race a
restore plus newer cleanup and overwrite the newer operator-visible fact.
The run-end outcome now rides remove()'s finalization update; every other
cleanup write stays live-row conditional, so no post-finalize write path
remains.
* test(qa): restore strict cached-health contract assertions
The lint sweep's Boolean() coercions let truthy non-booleans satisfy the
wire-typed cached-meta contract. Assert the literal boolean for unknown-typed
fields and use nullish-coalesced strict equivalents for boolean chains.
* fix(agents): clear the stale cleanup outcome when restoring a worktree
A restored checkout begins a new lifecycle; leaving the removed-lossless
fact on the live row showed operators a stale result until the next
cleanup. Restore clears the recorded outcome and the regression asserts
the cleared state before the next cleanup records fresh truth.
* fix(agents): scope stale cleanup outcomes to their observed lifecycle
A stale remover's retained/failed write raced a concurrent remove-plus-
restore: the revived row is live again, so the live-row condition alone
could stamp a prior-lifecycle outcome. Condition those writes on the
activity stamp the remover observed; restore bumps lastActiveAt, making
any prior-lifecycle write a no-op.
* fix(agents): advance the restore activity stamp within one millisecond
Stale cleanup writes fence on the activity stamp they observed; a restore
completing in the same millisecond could revive the row with an identical
stamp and let the fence match. Restore now always advances past the
stored value, and the ABA regression pins the clock to prove the
same-millisecond case.
* perf(doctor): slim remaining heavy doctor contract closures
Follow-up to #120698: several doctor closures still cold-loaded multi-second
kysely-bearing graphs through other broad barrels (session-store-runtime,
realtime-voice, channel-outbound, logging-core, memory-host-core/-events,
sqlite-runtime, persistent-dedupe, and plugin-local barrels).
- lazy-import heavy helpers inside async migration bodies (codex, msteams,
zalouser, workboard, matrix inbound-dedupe, memory-core migrations)
- bypass plugin-local barrels to defining modules (reef protocol,
memory-core short-term-promotion)
- move to lighter existing subpaths (slack -> channel-streaming, matrix
logger -> security-runtime, memory-wiki -> agent-scope-runtime, which now
also exports resolveSessionAgentId)
- add narrow openclaw/plugin-sdk/realtime-voice-activation for discord's
sync wake-name doctor rules
- split src/infra/kysely-sync-cache-state.ts so sqlite-transaction clears
Kysely caches without value-loading kysely; split the memory-host-sdk
kysely bridge off the schema/transaction bridge
- guard: forbid the heavy barrels in doctor closures with per-kind scoping
Cold enumeration per plugin: discord 52.6s->0.3s, msteams 30.9s->0.5s,
codex 29.6s->2.6s, zalouser 28.8s->2.3s, matrix 27.2s->3.2s,
slack 17.5s->1.5s, reef 9.9s->0.7s, memory-core 6.4s->3.6s,
workboard 3.4s->0.25s; all kysely-free except llm-task (named follow-up).
* fix(plugins): route slack streaming compat through a focused streaming-config subpath
The channel-streaming compat barrel is deprecated for extension production
code (deprecated-api-usage guard + SDK package contract). Add the narrow
non-deprecated openclaw/plugin-sdk/channel-streaming-config subpath for the
pure streaming config readers, and drop the now consumer-less
short-term-promotion barrel re-exports knip flagged.
* test(plugins): register memory-host-sdk kysely bridge in package boundary inventory
* fix(plugins): classify realtime-voice-activation as private-local
ClawSweeper P2: the subpath exports only a default target, which is the
private-local shape; register it in plugin-sdk-private-local-only-subpaths,
the package-boundary d.ts alias maps, and correct the public surface budgets
(realtime-voice-activation no longer counts as public).
* fix(release): exclude realtime-voice-activation declarations from the published package
Private-local subpaths ship without d.ts; register the files negation the
release pack-path check requires.
* fix(ui): report dev checkout update status
Show tracked-upstream commit lag and verified install/commit timestamps in Settings > Updates. Verify the post-restart Git revision before reporting success, and surface same-revision updates as an explicit no-op unless plugin convergence changed the install.
* test(ui): expect authoritative update reconciliation
* fix(process): disclose aggregate output loss
* test(process): isolate foreground retention regression
* fix(process): disclose cap loss while running
* fix(codex): pin catalog CODEX_HOME onto terminal resume spawns
The Codex session catalog lists threads through the plugin's supervision
app-server connection, which can run on the agent-scoped Codex home
(homeScope: "agent"). The terminal resume plan spawned a bare
'codex resume <threadId>' under the operator login shell, so the CLI
resolved its default ~/.codex and failed with "No saved session found".
Terminal plans now carry the catalog connection's CODEX_HOME (local and
paired-node paths), the gateway merges plan env into the PTY spawn, and
the triplicated home-scope resolution collapses into one shared
resolveCodexAppServerLocalHomeDir.
* feat(ui): add main-content terminal placement
The terminal panel gains a third dock placement, "main", filling the
content region next to the sidebar like the transcript pane. The panel
header gets a bottom/right/main placement switcher (persisted in the
existing openclaw.terminal.panel.v1 store), and opening a Codex/Claude
session from the session catalog selects main mode so resumed sessions
appear as a full window instead of a bottom strip.
* fix(cloud-workers): honor Crabbox SSH fallback ports
Select a currently reachable advertised SSH endpoint before returning a Crabbox worker lease, while preserving fail-closed teardown. Allow deletion of failed cloud sessions only after the environment record proves the provider lease is gone, and align the Cloud Workers docs with the shipped configuration and Control UI flow.
* fix(cloud-workers): retry advertised SSH endpoints
Persist provider-advertised SSH fallback ports and retry them through the shared pinned transport used by bootstrap, tunnels, reconnects, and workspace transfers. Keep candidate selection provider-neutral, preserve identity and host-key fencing, and add the same-version SQLite column through the existing additive migration path.
* fix(crabbox): recheck SSH readiness after setup
Treat provider setup as a mutating lifecycle phase: refresh authoritative inspect state and wait for SSH readiness again before returning the lease. This prevents bootstrap from racing setup-triggered SSH restarts on fresh and replayed workers while preserving security attestation and fail-closed teardown.
* fix(cloud-workers): retry bootstrap transfers across ports
Use the shared advertised-port candidate runner for bundle transfer, install, and best-effort upload cleanup as well as preflight. This keeps fresh bootstrap connections on pinned identity and host-key semantics when a previously selected SSH port disappears between phases.
Proof passed: 517 focused tests (1 skipped), final autoreview/TruffleHog clean, targeted lint/format. Full check-changed reached the unrelated max-lines baseline drift on current origin/main: this branch's merge base contains ui/src/pages/chat/components/chat-model-controls.ts while current origin/main removed that baseline entry; do not touch the unrelated baseline.
* fix(cloud-workers): persist fallback ports compatibly
Create the fallback-port companion table lazily as an additive surface, preserving downgrade tolerance for binaries that still expect the older shared-state schema.
* fix(cloud-workers): preserve bundle artifact narrowing
Bind the immutable discriminated artifact before the SSH retry callbacks so bundle-only fields remain narrowed across fresh candidate invocations. Use the shared temp-directory owner in the tunnel fallback test for deterministic cleanup.
* fix(cloud-workers): retire deleted placements
Terminal placement rows retire by exact CAS after session deletion, and managed AWS docs now pin the Gateway SSH CIDR.
* fix(crabbox): await pending AWS attestation
missing authoritative metadata is pending only while the lease is non-ready; ready leases still require explicit false.
* fix(cloud-workers): fence remote command retries
require explicit idempotent/never classification; remote worker launch never retries after ambiguous SSH loss.
* fix(cloud-workers): bound SSH fallback operations
Remove the localStorage-backed per-browser hide action ("Hide in this browser only") so the transcript mirrors agent context. The action was un-undoable because restore() had no UI path.
Stale openclaw:deleted:* localStorage keys become inert.
* docs(discord): drop retired ui.components.accentColor and fix heartbeat key name
* docs(discord): remove the retired accent-color example block
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* perf(plugins): declare doctor contract surfaces
* perf(doctor): slim migration import closures
* perf(plugins): narrow doctor declaration record surface and wire owner-test lane
Registry records carry only the doctorContract declaration instead of the whole
parsed manifest, and check:changed now selects the src/plugins-owned declaration
honesty and closure-guard tests for extension module/manifest changes so
cross-lane drift cannot pass PR classification.
* fix(doctor): keep control-plane dist imports require-safe
Keep doctor and channel control-plane chunks off exec-class dependencies, and enforce native require(esm) loading during postbuild.
* chore(plugin-sdk): regenerate API baseline
* chore(plugin-sdk): sync export ordering
* fix(plugins): satisfy doctor contract CI boundaries
* perf(doctor): make qqbot doctor closure dependency-light
qqbot was the last plugin above 5s in doctor state-migration enumeration
(~8s under tsx/jiti). The cost was not the state-key builder (already a
leaf): its doctor closure value-imported the runtime-doctor SDK barrel,
whose plugin-state-store/state-db re-exports pull kysely (~330 modules),
plus security-runtime for one fileExists (~200 modules), all resolved
per-module by jiti during enumeration.
Split the migration-define helpers and light re-exports into a new
private-local plugin-sdk/runtime-doctor-migrations subpath; runtime-doctor
re-exports it so its public surface is byte-identical (API baseline hash
unchanged). qqbot's doctor-contract and state-migrations now import only
the light subpath, swapping fileExists for the equivalent async
legacyStateFileExists already in the closure.
qqbot enumeration: ~8.0s/531 modules -> ~0.25s/18 modules.
* chore(plugin-sdk): drop private-local subpath from API baseline
runtime-doctor-migrations is private-local-only; the baseline tracks public
modules, and the earlier line was generated before the classification.
* fix(plugins): register runtime-doctor-migrations boundary paths
The private-local subpath list feeds the extension package boundary map;
the shared paths config and xai's derived overrides must carry the same
entry or the boundary contract test fails.
* feat(claws): export reviewed native bootstrap
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(claws): fail export when the package bootstrap drifted
Export re-emitted BOOTSTRAP.md only while the seeded copy was still pending, so
an agent whose bootstrap had been edited, flagged unsafe, or become unreadable
exported a package with no bootstrap at all. That is the same class of silent
loss the managed workspace files already guard against, so treat it the same
way: drifted bootstrap state now fails with `bootstrap_drifted` unless the
author supplies a reviewed `--bootstrap` replacement. A consumed bootstrap
stays a completed lifecycle state and still exports without BOOTSTRAP.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(claws): bind pending bootstrap export bytes
* fix(claws): preserve current export ownership limits
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3cdcdb00-ade8-4e61-85a7-8151b35f216a
* fix(ai): preserve Responses server compaction state
Persist opaque Responses compaction items as fenced provider replay state so long stateless sessions can resume authoritative compressed history without exposing it in display or diagnostics. Carry state through worker transcripts and prune replay prefixes without splitting tool pairs.
Release note: Preserve long OpenAI Responses sessions across server-side compaction and worker restarts.
Related: #95788
* test(ai): align long-context fixtures with CI contracts
Make tool-result fixtures type-complete, use the canonical model selector helper, remove unused test-helper exports, and route the paid long-context live probe through the dedicated Gateway profile shard.
* test(ai): type mocked Responses terminal events
Give the mock SSE event collection an explicit open event shape so terminal response events coexist with output-item events under the root test typecheck.
* fix(ai): suppress rejected compaction replay
Persist a route-fenced suppression tombstone when encrypted-content recovery rejects a compaction item, so later turns do not retry the same opaque state. Preserve the tombstone through transcript redaction and cover successful fallback followed by the next turn.
* fix(ai): keep compaction suppression transport-private
Keep the suppression contract local to its sole Responses transport owner and make the regression fixture satisfy root type and lint checks without widening the Plugin SDK surface.
* refactor(ai): remove compaction suppression re-export
* fix(ai): scope compaction suppression to replay route
Keep foreign-route rejection tombstones from hiding the newest compatible Responses compaction while preserving same-route suppression.
* fix(ai): harden Responses replay recovery
Stage encrypted replay recovery so compaction is only suppressed after an attributable rejection. Preserve terminal ordering and keep provider replay within worker frame budgets without truncating opaque state.
* refactor(ai): centralize Responses output indexes
Keep normalized output identity tracking in the stream-slot owner, move response failure state to its diagnostic owner, and remove the obsolete replay clone export so exact-head static gates remain shrink-only.
* fix(ai): retain idless terminal tool identity
Use the canonical empty identity only when a provider supplies neither call nor item id, preventing terminal recovery from duplicating a done-only tool call while preserving stronger identities when available.
* fix(sessions): hide provider replay from public events
* fix(ai): stage encrypted replay recovery
* fix(ai): keep replay attempt kind internal
* fix(ai): route Azure through replay recovery
Use the shared encrypted-content retry owner for Azure Responses so compaction suppression and prompt-observer variants stay coherent across transports.
* fix(ai): harden replay persistence boundaries
Fence Azure replay by the resolved request endpoint, drop invalid replay during transcript sanitization, and surface worker-launch replay omissions through the existing redacted diagnostic path.
* docs: runners plan — one placement model for sessions, devices, and cloud
Proposal for unifying where sessions run: gateway/devices/cloud as runners
behind the existing worker admission stack; session-continuation ergonomics;
one-paste device pairing; derived projects read model; naming rulings
(session, devices); deletion ledger. Encodes the adversarial-review
kill-list as non-goals with code evidence.
* docs: unwrap PR refs so markdownlint MD018 passes
* feat(update): scheduled update campaigns with countdown, dev-channel auto-update, and Updates settings page
* fix(ui): satisfy update campaign deadcode checks
* feat(update): extend scheduled update campaigns
* test(ui): include Updates page in settings sidebar search expectations
* fix(update): end campaigns when apply fails
* fix(update): pin dev campaign applies to the announced commit
* test(ui): align update e2e fixtures
* fix(update): pin adopted dev updates to the announced commit
* docs(update): document automatic dev update campaigns
* fix(ui): gate automatic dev updates on git installs
* fix(gateway): scope detailed update metadata to read-capable clients
* fix(gateway): break post-attach madge cycle with a leaf client-lookup type
* chore(protocol): regenerate clients after rebase
* test(gateway): update method tail after rebase
* fix(update): preserve campaign target and hold state
* test(update): align adoption mocks and split handoff suite
* test(update): route split handoff suites on Windows
* fix(agent): apply steering before unstarted tools
Restore steering checkpoints before sequential tool launches and before parallel batch launch. Preserve paired synthetic tool results, async callback compatibility, and Code Mode outcome handling.
* fix(agent): delay tool loop admission commits
Commit loop-detection history only for calls crossing the final launch checkpoint. Release steering-skipped markers, add repeated-steer coverage, and align remaining steering contract text.
* fix(agent): keep tool admission lifecycle internal
Attach delayed admission callbacks through the private internal-hooks seam so steering history remains correct without widening the public Agent Core or Plugin SDK contract.
* fix(agent): preserve steering API contracts
Keep public steering callbacks Promise-based and protocol error kinds unchanged. Use private synchronous draining and structured skip details to retain launch-boundary behavior without API or generated protocol drift.
* test(gateway): use canonical steering fixture config
Use keyed agent entries in the real gateway steering harness so current main does not migrate the fixture during startup.
* fix(agent): remove unused lifecycle re-export
* fix(agent): gate tool launch after wrapper preflight
Split OpenClaw tool execution into private prepare and launch phases so steering is checked after policy, approval, validation, and reconciliation but before the original side effect. Preserve final arguments, voice grants, loop admission, context wrappers, and direct tool execution.
* fix(agent): preserve steering callback receiver
Invoke public steering callbacks with their AgentLoopConfig receiver and cover method-style implementations that read config-owned queue state.
* feat(macos): add native camera PTZ controls
Add physical UVC pan, tilt, and zoom through the signed Mac app, with camera.ptz.control kept behind dangerous-command approval. Verified against real Insta360 Link 2 Pro hardware.
* refactor(agents): split message tool display config
* fix(mac): harden camera PTZ contracts
* fix(subagents): wake the parent when a follow-up finishes a yielded child
A sub-agent that calls sessions_yield on its own behalf parks its run and
correctly withholds the parent's announce. But a later follow-up to that same
child session registered a sibling registry row instead of continuing the paused
one, so the requester defaulted to the child's own main session and the original
parent — itself idle behind sessions_yield — was never woken. The paused row also
stayed an unsettled descendant, deferring the parent's settle batch forever with
nothing recorded explaining the silence.
Follow-up dispatch now adopts the paused row through the existing post-steer
replacement seam, inheriting the requester identity and carrying the settle-wake
credential forward with its frozen batch membership remapped to the new run id.
A follow-up that names its own requester keeps registering separately, since an
explicit requester is a delivery opt-in that adoption would silently drop.
Also stops frozen-result refill from targeting paused rows: a yield clears the
result on purpose, so refilling from the session would attribute a later turn's
text to the paused run.
Closes#120157
* fix(subagents): select the paused owner past a requester-bound sibling
Adoption looked up the newest run for the child session and adopted it only
when that row was itself paused. A requester-bound follow-up deliberately stays
a sibling, but it registers at a higher generation and becomes that newest row,
so any later default follow-up saw an unpaused newest row, declined adoption,
and registered yet another sibling. The original requester stayed parked behind
a paused row that can never announce -- the same silent stall this fix exists to
remove, reached through a valid mixed-delivery sequence.
The latest-run query now takes an optional predicate applied before the
generation comparison, so a caller that owns a specific row class selects the
newest row of that class. Adoption asks for the newest `sessions_yield` row
directly instead of inferring it from generation order.
Docs now state that continuation applies to default delivery, since a follow-up
carrying its own requester runs as a sibling by design.
* test(qa): prove post-yield follow-up delivery through the gateway boundary
The unit and gateway-method tests for paused-run adoption assert on registry
rows, which proves the bookkeeping but not that an operator ever sees the
result. This adds the boundary proof: a real gateway child, the QA mock channel,
and the mock provider driving a subagent that pauses itself and finishes only on
a later follow-up.
A fixture plugin owns both legs. Its `before_dispatch` hook spawns the child with
`completionDelivery: "current-requester"`, so the announce has the operator turn
as its audience. An HTTP route then dispatches the follow-up to that same paused
session using default delivery -- the path adoption is meant to catch. A
requester-bound follow-up would opt into its own audience and run as a sibling
instead, so the two legs must differ here.
The mock provider gains a child that yields on its own behalf. Both of its turns
match on the current prompt rather than the shared transcript, so the yielded
kickoff cannot make the follow-up turn yield a second time.
The scenario asserts both sides of the invariant: no outbound traffic while the
child is paused, and exactly one announce carrying the follow-up marker once it
ends.
Reverting the adoption call site fails this test in the way that matters: the
child still produces its marker and the run still ends with stopReason=stop, but
nothing reaches the requester and the wait times out. The result is computed and
then silently dropped -- which is the failure this repair exists to remove.
* fix(ci): match QA Lab fixture plugin entries as a group in knip
The all-exports pass listed one fixture entry by name, so every new QA Lab
fixture plugin lands as an unused file and turns check-dependencies red until
someone remembers this file. Nothing imports these entries by design: the
Gateway E2E loads them through plugin config paths.
* docs(subagents): scope yield continuation to plugin runtime follow-ups
Adoption is gated on plugin_subagent task tracking, which only
createGatewaySubagentRuntime().run sets, so api.runtime.subagent.run is the
sole route into it. Writing that as one example implied other follow-up paths
to a paused session continue the run too; they are not tracked as sub-agent
runs and announce nobody.
* fix(subagents): reject undurable paused-run adoption
Fail plugin follow-up admission closed when the paused-run ownership swap cannot be persisted, while retaining the existing restart-recovery return-false contract. Trim duplicate tests and keep boundary coverage for requester routing, wake-batch remapping, repeated yield, and persistence rollback.
Co-authored-by: zhou.huanfeng <woundfongv3@163.com>
* docs(subagents): clarify yielded-run steering
Co-authored-by: zhou.huanfeng <woundfongv3@163.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* improve(ui): consolidate dashboard chat visibility into one Chat/Split/Dashboard toggle
The dashboard face spread chat visibility across three separate
affordances: a two-option face toggle, an icon-only dock dropdown among
the header actions, and a floating vertically-rotated "Show chat"
button on the board edge that users could not discover. One concept,
three controls.
The header now renders a single segmented control - Chat | Split |
Dashboard - where Split is dashboard plus docked chat and Dashboard is
board-only (the former "Hide chat"). A compact caret attached to the
pill picks the dock side (left/right/bottom) and only appears in Split
mode when dock mutation is allowed; read-only sessions keep the plain
two-option Chat | Dashboard toggle. The floating reopen button, the
standalone dock menu button, and their CSS (including the mobile
overrides) are deleted, so the header ends up with fewer buttons than
before.
Verified in the mock-gateway dev harness: mode mapping, caret dropdown,
unhide-to-remembered-side, and hidden dock rendering board-only.
* fix(ci): unexport board dock settings union and repair stale plugin-sdk baseline
BoardVisibleChatDock lost its last external consumer when the board
view switch consolidated dock controls; keep it file-local so the
production unused-export scan stays clean.
Also regenerate docs/.generated/plugin-sdk-api-baseline.sha256: the
session-catalog module hash was stale on main (check fails on clean
origin/main after the agent-attribution reverts), which blocks every
PR's check-plugin-sdk-api-baseline job.
* docs(web): describe the Chat/Split/Dashboard switch and dock-side picker
* fix(plugin-sdk): stabilize session catalog baseline
* test(doctor): isolate bind persistence inventory
* fix(ci): prepare max-lines base during checkout
* test(sessions): align freshness regression fixtures
* style(tests): format context usage fixture
* fix(ci): preserve token provenance and isolate fork credentials
Finish the canonical token-provenance fixtures while preserving the
latest maintainer-owned unavailable-usage ordering. Limit the diff-base
GitHub token to manual non-release comparisons and guard that fork
isolation invariant in the existing workflow regression test.
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
* test(ui): identify actual managed image eviction
* fix(ci): reuse the validated protocol comparison base
Consume the immutable diff base already resolved and validated by
preflight for every CI event. Remove the dead duplicate manual
GitHub lookup and unreachable fallbacks, and guard the single
authoritative path without changing fork credential isolation.
* test(sessions): preserve stale total expectation
* test(ui): arm reconnect deferral before disconnect
---------
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
* fix(daemon): skip unreadable foreign LaunchDaemon plists in ownership scan
inspectSystemLaunchDaemonOwnership raised SystemLaunchDaemonOwnershipError when any /Library/LaunchDaemons plist was unreadable, such as third-party VPN helper plists with mode 600. This broke gateway install/repair and launchd.integration.e2e on affected Macs. Unreadable plists are foreign by contract, while loaded same-label daemons remain caught by the bracketing launchctl probes.
* fix(ci): regenerate plugin-sdk api baseline after 267268f646
* test(doctor): scope gateway-bind persistence test to its writer seam
Culprit: 267268f646.
* docs(daemon): document unreadable plist tradeoff
* fix(ui): hide connection form during initial auth
Use the authoritative gateway connecting/error state so trusted-proxy, Tailscale, bootstrap, device, and token auth all keep the centered loading screen until the first handshake resolves. Real failures still reveal the login gate, manual retries stay pinned, and established reconnects retain the shell.
* fix(ui): preserve approval document login gate
Scope the neutral unresolved first-connect splash to the normal Control UI document. Standalone approval links continue to expose their authentication gate without losing the deep link, while embedded terminal behavior remains unchanged.