* refactor(config): retire dead streaming.progress.render key
The key had zero runtime consumers after #122552. Core doctor now strips it via stripRetiredTuningKnobs, and production LOC is net -13.
* fix(tooling): pin plugin SDK surface counts to the reduced export set
The retired progress-draft render reader counted twice via channel-outbound and channel-message's wildcard re-export.
* fix(skills): keep ClawHub publisher identity from search through install
ClawHub search returns one entry per publisher, so several results can share a
slug. Every client collapsed the selection to that bare slug before calling
skills.detail and skills.install, and ClawHub answered 409 AMBIGUOUS_SKILL_SLUG
with no in-product way forward.
searchClawHubSkills now records the publisher-qualified reference once, on the
result that carries it, and the Gateway protocol documents it. skills.detail
parses the same reference grammar skills.install already accepted, so review and
install cannot resolve to different publishers. Control UI carries that one
reference through row actions, detail, busy state, and acknowledgement retries,
and shows it so otherwise identical rows are distinguishable.
Fixes#117633
* fix(apps): send the ClawHub publisher reference from native skill browsers
macOS, iOS, and Android read the qualified reference from search results and use
it for skills.detail, install, busy state, installed matching, and list identity,
so two publishers sharing a slug stay distinct instead of collapsing into one
ambiguous request.
* fix(skills): refuse external-source skill detail instead of reading a same-slug skill
ClawHub has no source-qualified read endpoint, so a skills-sh reference parsed
down to its bare slug would have returned a registry skill's card while install
resolved the external artifact. Review and install could name different skills.
skills.detail now fails closed on any reference that carries a source, and the
macOS and AgentPro rows show the publisher reference next to the summary instead
of only when a summary is missing, so same-slug rows stay distinguishable.
* chore(apps): refresh native i18n source baseline for the skill row references
* refactor(skills): drop the unread search-result ownerHandle field
installRef is the one reference clients send back, and no client reads the
publisher handle separately, so the protocol and Control UI carry one field
instead of two.
* fix(skills): name the next step when external skill detail is refused
Clients that gate install behind a successful review would otherwise see only a
refusal, so the error names the direct install path and the CLI equivalent.
* fix(macos): use a doc comment on the ClawHub row subtitle
swift-format's docComments rule requires doc comments on declarations; the
subtitle property carried a regular comment and failed macos-swift.
* fix(skills): carry ClawHub trust state to clients that can install
Forwarding installRef let clients install the exact publisher the operator
picked, including external skills-sh sources. It did not forward the trust
state that says ClawHub never scanned that source, so iOS AgentPro — the one
surface that installs in a single tap with no review step — could install an
unscanned artifact with nothing on screen saying so. The CLI already labels
these (docs/clawhub/cli.md, docs/cli/skills.md); native clients could not,
because trustState was never on the wire.
trustState becomes an optional field on SkillsSearchResultSchema. It is purely
additive: older clients ignore an unknown key and the field is absent for
registry results, so downgraded readers are unaffected and no protocol version
moves.
Every client that renders a search row now shows "Not scanned by ClawHub",
matching the CLI wording exactly: iOS AgentPro in the row above the install
button, macOS and Android beside the review action, and Control UI on the row
that explains why review is refused for these sources.
Covered by a wire assertion that the state reaches clients for an external
source and stays absent for registry rows, plus decode-and-label tests on the
shared Swift kit and the Android parser, and a Control UI render assertion.
* fix(ui): size the ClawHub detail dialog to a refusal message
Refusing detail for an external source made an error-only dialog reachable.
The shared preview panel reserves a tall reader height for skill documents, so
a two-line refusal rendered in a mostly empty dialog and read as broken rather
than deliberate. Found by inspecting the review captures.
* revert(ui,apps): drop the ClawHub trust label layer
Maintainer product decision: skills.sh runs its own scanners, so OpenClaw does
not add a second alert layer in the apps. Removes the label from Control UI,
iOS, macOS and Android, and drops the trustState wire field that nothing would
render. The CLI keeps its existing label; changing that is a separate call.
Publisher identity, the fail-closed detail refusal, and the message-only dialog
are unchanged. Splits the oversized skills view test file to satisfy max-lines
without a suppression.
* test(ui): fix ClawHub skill fixture checks
* chore(plugin-sdk): refresh API baseline
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* feat(ui): open subagent details in chat sidebar
* chore: drop changelog edit (release generation owns it)
* refactor(ui): drop duplicate close in subagent detail panel
The sidebar region header already owns a Close Details control in both
wide and narrow layouts; the panel-local X duplicated it 40px away.
* fix(ui): stop subagent transcript loader when pane presentation retires
Pane retention wipes sidebarContent directly, so the detail slot's
render-time reset can never run again; a pending refresh timer plus
incoming task events kept refetching chat.history for a hidden panel.
* docs(ui): note close-control ownership in subagent detail header
* fix(ui): break transcript renderer import cycle
* fix(ui): use shared action cursor for subagent rows
* fix(sessions): zero highWaterBytes no longer clears all session history
resolveHighWaterBytes passed an explicit non-positive highWaterBytes through
verbatim. The resolved value is the disk-budget cleanup loop's stop condition,
so a zero target made enforce mode evict every unprotected session and prune
its extracted archives instead of trimming to the documented 80% default.
Route the non-positive case to the function's existing unusable-value branch
(computeDefault). Not null: that disables the budget and permits unbounded
growth, which is right for a cap but wrong for a target.
Sibling of #119422, which fixed the same harm for maxDiskBytes and guarded
only resolveMaxDiskBytes.
* test(infra): isolate worktree migration discovery
Keep worktree migration coverage focused on its real filesystem, Git, and SQLite owner while avoiding unrelated channel and plugin doctor cold starts on fork CI.
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
* test(ci): carry owner-approved SDK and doctor gate repairs
Carry the already-approved plugin SDK contract manifest and focused doctor-flow test isolation from the maintainer-owned CI repair. Preserve real config migration, persistence, snapshot, and SQLite cleanup coverage; no production behavior changes.
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
* fix(sessions): use the renamed withTestDir helper in the new budget test
* fix(sessions): align high-water zero contract
* style(sessions): format high-water changes
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
The template shipped without the operator-facing half: no way to prove the
restore path works before depending on it, no cost signal for the always-on
versus webhook-only choice, no log surfaces, and no failure-mode index.
Adds an architecture diagram, a restore rehearsal, measured recovery numbers
from the real-R2 proof, provisioned-resource cost guidance, wrangler tail
observability, and a troubleshooting index covering image architecture,
Litestream S3 credentials, the startupz/readyz split, and ephemeral-disk
config loss.
* fix(gateway): cascade session stop to subagents
Cascade exact and session-wide aborts through the subagent registry and keep Control UI Stop visible while descendants remain active.
* refactor(gateway): absorb stop cascade orchestration
* chore(gateway): refresh plugin SDK API contract for abort refactor
Regenerates the export-closure contract after the stop-cascade refactor
moved gateway abort orchestration exports, and drops a redundant Boolean()
wrapper flagged by lint. Public SDK surface gate is unchanged.
* chore(gateway): regenerate plugin SDK API baseline
* feat(ui): show environment facts in Where picker
Preserve environment platform, trust, session-host, and capability metadata through new-session discovery while keeping placement availability tied to live executable nodes. Render bounded quiet device/cloud facts and add mocked browser coverage for task #33.
* docs(plan): track picker environment facts
* refactor(ui): split place browser rendering
* feat(deploy): add experimental Cloudflare template
* fix(deploy): keep container SSH debug access opt-in
* fix(deploy): satisfy scripts tsgo lane and model wrangler entrypoint in knip
* fix(deploy): model wrangler-consumed exports and isolated dependency in knip
The Worker default export and Durable Object class are instantiated by
wrangler from wrangler.jsonc, and @cloudflare/containers lives in the
template's isolated package.json — modeled per the deadcode checks' own
guidance rather than root-manifest changes.
* docs(deploy): align SSH bootstrap flow with the disabled-by-default policy
* refactor(plugins): retire deactivate hook alias
* refactor(plugin-sdk): prune retired facade exports
* test(logging): isolate logger test controls
* refactor(logging): internalize file transport controls
* test(plugin-sdk): preserve retired facade coverage
* test(auto-reply): remove stale diagnostic imports
* refactor(logging): delete dead config-read guard
shouldSkipMutatingLoggingConfigRead had no production caller even on main;
it survived the dead-export scan only via logger's testApi re-export. The
test-isolation commit removed that mask, exposing the fossil. Delete the
guard, its test-only re-export, its mock entry, and its dedicated test file.
* refactor(plugin-sdk): retire due compatibility subpaths
* test(plugin-sdk): type group policy predicates
* refactor(plugin-sdk): split removed subpath records
* refactor(secrets): remove retired collector barrel
* test(plugin-sdk): tighten wildcard surface pin
* refactor(plugin-sdk): retire matrix facade metadata
* style(plugin-sdk): format facade metadata
* fix(ci): load channel setup contracts from source
Repair the main-owned regression from 99d662473c (Peter Steinberger): the new env-contract test could consume stale ignored dist metadata instead of the checked-in plugin declaration.
* test(plugin-sdk): refresh API baseline after rebase
* feat(linux): suspend the local gateway across host sleep via logind
Mirror the macOS sleep cycle in the Tauri companion: a logind
PrepareForSleep listener with a delay inhibitor best-effort prepares a
local gateway suspension before sleep and resumes it on wake with a
reconnect nudge and bounded retries; leases are route-bound, always
cleared on wake, and late prepare responses resume immediately.
* fix(linux): keep the sleep listener alive when inhibitor re-acquire fails
* fix(linux): activate the driver for sleep cycles and bound driver-side sleep RPCs
Sleep cycles temporarily unpark the gateway driver so prepare/resume work
without the Quick Chat window; suspend RPCs carry a 3s driver-side budget
that recycles a hung socket instead of starving wake retries; a mock-logind
integration test exercises the Linux listener end to end.
* fix(linux): count overlapping sleep cycles instead of a boolean flag
An older wake task ending late cleared the shared activation flag and
parked the driver mid-cycle; a saturating depth counter keeps the driver
active until the last cycle ends.
* fix(linux): gate sleep-cycle activation on loopback routes and never block wake on logind
Remote or unconfigured routes no longer activate the driver during
sleep, the paired end hook runs only for cycles that began, and wake
recovery is spawned before the inhibitor re-acquire so a slow logind
cannot delay reconnect/resume.
* feat(slack): render live session cards as the default Slack progress mode
Slack streaming.mode default flips partial->progress.
Progress mode renders one live Block Kit session card with a status header, narration, plan, activity, diff stat, and elapsed time; it is edited in place and finalized to success or error with an Open in OpenClaw button when gateway.publicOrigin is set.
Final assistant text always delivers separately.
The shared progress compositor gains a success-only additive per-turn diffStat mirroring the task ledger fold.
resolveGatewayPublicOrigin is exported through the plugin SDK.
The diffs viewer URL falls back to publicOrigin.
The old rich/text progress render fork is deleted.
Native task cards remain unchanged and opt-in.
* chore(config): regenerate bundled channel config metadata
* refactor(slack): keep session card state type internal
* refactor(slack): split session-card and diff-stat owners under lint ceilings
* refactor(channels): reuse diff-stat type from its owner module
Import ChannelProgressDraftDiffStat from progress-draft-diffstat instead of
redeclaring it in the compositor, resolving the all-exports deadcode scan.
* chore(plugin-sdk): regenerate api baselines for channel barrels
Baselines drifted after the rebase reconciled them against main; regenerate to
match the branch's actual channel-message/channel-outbound surface.
* fix(slack): drop a session card that cannot terminalize after final delivery
If the final reply is delivered but the terminal card edit fails, the caller
now clears the stale card instead of leaving it stuck in its Working state
(mirrors the pre-card preview cleanup). Adds a transport-failure regression and
corrects three tests that asserted the prior ignore-the-result behavior.
Documents resolveGatewayPublicOrigin as a dependency-light runtime helper on the
config-contracts SDK subpath, which previously described a type-only surface.
Addresses ClawSweeper P2 (unfinalized card) and P1 (runtime SDK contract).
* fix(slack): suppress default tool messages under the default progress card
resolveChannelStreamingSuppressDefaultToolProgressMessages re-derived the stream
mode from config with an "off" default, unlike its sibling resolvers which take
a caller-resolved mode override. After this branch made progress the Slack
default, a default-config channel turn saw mode "off" and left a stray
"Using tool: X" plain message posting alongside the session card. Thread the
caller-resolved mode through (compositor passes params.mode; Slack dispatch passes
slackStreaming.mode), matching resolveChannelStreamingPreviewToolProgress.
Retarget the progress-session-card delivery-trace golden at an EMPTY Slack config
so it proves the real default path; the regenerated golden is byte-identical,
confirming defaults now yield the clean card sequence (one card post, separate
final text, one terminal update with the Open in OpenClaw button, no stray tool
message). Switch the dispatch delivery-mode mock to the real resolver so the card
tests exercise the true channel default (automatic), not a hand-rolled one.
* chore(plugin-sdk): regenerate api baselines for the streaming mode param
resolveChannelStreamingSuppressDefaultToolProgressMessages gained an optional
mode override; the changed signature reflows the surface hash of every barrel
that re-exports it, so regenerate the affected baselines.
* chore(config): regenerate config baselines
* fix(channels): validate headless channel setup
* docs(channels): document headless provisioning
* fix(channels): repair setup metadata typing
* chore(channels): regenerate official channel catalog for env metadata
* fix(slack): keep mode-conditional env contract plugin-owned
Static --use-env declaration keeps only the unconditional SLACK_BOT_TOKEN;
socket-vs-HTTP conditional requirements (app token, signing secret) stay in
Slack's own setup validation so HTTP mode no longer demands an irrelevant
SLACK_APP_TOKEN.
* chore(sdk): regenerate api baselines and catalog after rebase
* fix(slack): align manifest env declaration with runtime contract
* chore(sdk): regenerate api baselines after rebase
* chore(sdk): regenerate api baselines after rebase
* chore(sdk): regenerate api baselines after rebase
* refactor(voice): promote realtime voice policy to talk owner
Move the exact-speech protocol (typed consult-call outcome instead of
string scraping), agent-proxy session instructions, and consult/wake-name/
barge-in policy defaults from the Discord plugin into src/talk, exported
additively via openclaw/plugin-sdk/realtime-voice. Wake-name enablement now
keys on a supportsActivationNameGating provider capability instead of a
hardcoded provider id. Follow-up to the voice split (#122479).
* fix(talk): bind exact-speech bypass to retained session state
Per ClawSweeper review on #122668: the protocol marker is untrusted model
tool-call text; only a parsed answer matching a currently retained
exact-speech text may select the privileged replay path. Unretained
marker calls route to a normal consult.
* feat(gateway): recover channels and health promptly after host sleep
A dependency-free thaw detector rides the existing 30s maintenance tick:
when the process resumes after being frozen >=45s beyond cadence (laptop
sleep, VM pause, SIGSTOP), the gateway restarts running channel accounts
(dead sockets otherwise take up to ~35 minutes to notice), refreshes
health/presence, and resets the event-loop histogram so the freeze does
not read as degradation. Admission is rechecked before every recovery
side effect; a suspension beginning mid-recovery re-pends the thaw, and
timed-out channel stops complete their two-call restart in one pass.
The macOS app cooperates: NSWorkspace sleep/wake observers in
GatewayConnectivityCoordinator best-effort prepare a local gateway
suspension before sleep and resume it on wake, never blocking sleep.
The lease is bound to the route that prepared it and always cleared on
wake; route or mode changes across sleep drop it to self-expiry.
Live proof: SIGSTOP 85s on an isolated dev gateway -> 'host thaw
detected: process was frozen ~57683ms', channels restarted, health ok,
eventLoop degraded=false after thaw.
* fix(macos): resume a sleep lease whose prepare response arrives after wake
A prepare completing after didWake previously discarded the lease id,
fencing the gateway until the two-minute expiry after micro-sleeps; the
late response now resumes immediately. Document the conservative
route-token drift tradeoff.
* fix(macos): retry wake resume after refreshing the dead post-sleep transport
After real sleep the WebSocket is usually dead exactly when resume runs;
refresh the endpoint first, then attempt resume up to three times with
bounded delays, clearing the lease only on success or exhaustion. A new
sleep cycle aborts in-flight retries.
* fix(gateway): bound plugin stopAccount so channel stops cannot wedge recovery
stopChannel awaited plugin stopAccount unbounded; a never-settling stop
hung the thaw restart (and health-monitor sweeps) and held the
single-flight recovery guard forever. Race it against the existing
5s stop timeout; the timed-out path flows into the established
recoveryStopTimedOut two-call restart contract. Regression wedges
pre-fix.
* refactor(gateway): move thaw channel restart off ChannelManager and fence mid-pass
restartRunningChannelAccounts is a standalone helper over the public
manager surface with a shouldContinue probe checked before every stop
and start, so a suspension committing while an account stop is awaited
leaves later accounts untouched. Regression covers the mid-pass close.
* fix(gateway): sanitize late writes from an abandoned stopAccount
An abandoned (timed-out) stopAccount can settle after its replacement
started; route its late setStatus writes through the existing
stale-task sanitizer so they cannot repaint or tear down the
replacement. Regression fails pre-fix.
* refactor(infra): extract shared git exec and verified snapshot-copy helpers
Moves the worktrees git wrapper to src/infra/git-exec.ts (with optional
maxOutputBytes for large buffered reads) and the online-backup/sanitize/
VACUUM/verify snapshot step into src/snapshot/openclaw-snapshot-copy.ts so
snapshot backends share one hardened copy path. Behavior-identical moves;
all importers updated.
* feat(snapshot): git-backed versioned SQLite snapshot engine
Deterministic per-table JSONL dumps (PK-ordered, lossless bigint/blob
encoding), verbatim DDL preservation, virtual/shadow-table skipping with
FTS rebuild on restore, secret-table redaction policy, manifest with
per-table row counts and content hashes, and restore verification by
re-serialization. Unchanged data produces no commit.
* feat(backup): recorded runs, freshness surfacing, and scheduled git backups
Every backup attempt is recorded in the previously writer-less backup_runs
table (bounded to 200 rows). openclaw status gains a Backups overview row
and JSON payload; doctor prints an informational hint when no successful
backup is recorded or the newest is stale. New commands: backup git
init/create/log/verify/restore and backup enable/disable, which provision
one idempotent gateway cron job running scheduled git backups.
* fix(state): stop bumping schema_meta.updated_at on unchanged opens
updated_at now records when schema metadata actually changed instead of
when the database was last opened; unconditional bumps dirtied the row on
every open and defeated no-change backup detection.
* docs: document versioned git backups, scheduling, and backup freshness
* fix(backup): satisfy CI ownership checks
* fix(backup): complete CI contract coverage
* fix(backup): complete credential table redaction
* fix(backup): isolate git repository ownership
* fix(backup): persist push degradation
* fix(backup): atomically converge schedules
* fix(status): isolate backup freshness environment
* fix(status): carry scan environment to freshness reads
* fix(backup): harden Git repository ownership
* docs(backup): document Git repository safety
* fix(backup): non-creating outcome log and origin preflight for pushed schedules
Recording a backup outcome never bootstraps an absent state database (a
failed backup on a fresh host would otherwise create a blank DB that a
retry treats as real input), and backup enable --push now requires the
repository to have an origin remote, pointing at backup git init --remote
instead of scheduling permanently degraded pushes.
* refactor(worktrees): use shared git exec helpers
* refactor(worktrees): remove unused git buffer wrapper
* refactor(worktrees): consume buffered git helper
* feat(backup): redact pushed schedules by default
Unattended recurring pushes retain credential-bearing tables durably in
remote Git history, so backup enable --push now defaults to
--exclude-secrets; --include-secrets is the explicit full-fidelity
override (still warned). Local non-push schedules keep full fidelity for
complete restores.
* fix(backup): redact audit HMAC and OAuth pending state; tolerate absent backup_runs
Adds audit_identity_keys (audit HMAC key) and mcp_oauth_pending_authorizations
(live OAuth callback state) to the redaction inventory, and makes read-only
backup freshness treat a same-version database without the additive
backup_runs table as no recorded backups instead of failing before a
writable open converges the schema.
* fix(backup): restrict schedules to local gateways
* fix(snapshot): harden Git restore and redaction
* fix(backup): block pushes of adopted history
* fix(backup): contain commits and pairing secrets
* test(secrets): cover code-mode nested exec store env; document harness boundary
* docs(secrets): warn that store env does not reach external agent harnesses
* feat(gateway): add gateway-host desktop source behind desktop.host lab
Introduce the host as a first-class desktop source so operators can view
the machine OpenClaw runs on, not just cloud-worker environments:
- protocol: desktop.observe / desktop.launch with a discriminated
DesktopSource union (host | environment) plus an additive auth hint;
EnvironmentSummary gains a top-level desktop flag
- config: desktop.host { enabled, port?, passwordFile? }, Labs-gated
- rfb-probe: pure RFB version/security-type parser used to detect an
already-running loopback VNC server and classify its auth
- host-source: attaches to 127.0.0.1:<port>, refuses unauthenticated
(None) and unsupported (VeNCrypt) servers, and refuses ARD with the
supported alternative until the macOS milestone
- host-guidance: per-OS enablement text so no path dead-ends
- doctor + status report host desktop availability and auth type only
worker.desktop.observe/launch stay as delegating aliases with identical
behavior. Also drops the now-unused WorkerDesktopTunnels type export.
Live-verified against macOS Screen Sharing: probe reads RFB 003.889,
returns security types [30,33,36,35], classifies ard-account.
* test(gateway): probe RFB handshakes through the socket boundary
The probe's banner and security-offer parsers were exported solely so
unit tests could call them, which the dead-export gate rejects and which
tests internals rather than behavior. Keep them module-local and drive
the probe through a scripted loopback server instead.
The boundary tests also cover what pure-function vectors could not:
handshakes split across packets, legacy RFB 3.3 single-word security,
server-rejected handshakes, early hangups, and connect timeouts.
* feat(ui): let the Desktop panel view this machine, not just cloud workers
The Desktop panel was gated on a cloud-worker session placement, so an
operator running OpenClaw locally had no way to see the machine hosting
their main session even with a VNC server running on it.
Availability now follows the advertised desktop.observe method plus
operator.admin instead of session placement, and the picker lists every
environment whose summary reports a desktop, with the gateway row shown
as "This machine". Sources are passed to the generic desktop.observe /
desktop.launch RPCs; the app launcher stays worker-only. When a host
attach needs a password the gateway did not supply, the panel prompts and
keeps the value in memory for that connection only.
Adds the hostDesktop Labs toggle for desktop.host.enabled.
* fix(scripts): keep the env-var ratchet usable in shallow checkouts
The env-var budget check resolved its base ref, then hard-failed when
`git merge-base` found no shared ancestor. Shallow clones and grafted
agent checkouts resolve origin/main but truncate the history behind it,
so an advisory growth ratchet took down the whole check:changed gate
with "Could not resolve env-var count merge base for: origin/main".
Only the growth comparison needs a baseline, and the script already has
a no-baseline path. Treat git's exit 1 with empty output (no shared
ancestor) as that case and say so on stderr; a genuine failure still
exits 128 and still throws, and the absolute count-vs-budget check runs
either way.
* test(ui): measure the inline-code chip against its line box
The inline-code spacing test compared the chip's height to a prose text
rect, so it silently measured the monospace font's default line spacing.
That is ~17px on macOS and several px shorter on Linux, so the assertion
passed on CI and failed locally at 4.5 against a 3.75 bound -- after the
bound had already been widened once to chase browser font metrics.
Compare the chip to the paragraph's CSS line box instead, which is what
"the chip must not disrupt the line" actually means and is platform
independent. The horizontal gap stays as-is: it is em-derived padding
plus border, and it is the assertion that catches detached punctuation.
Verified both directions on macOS: the file is fully green, and
restoring the pre-fix 0.15em/0.35em padding still fails the gap
assertion at 5.41.
* feat(gateway): view macOS Screen Sharing from the Desktop panel
Modern macOS only offers ARD account authentication for Screen Sharing,
so the host desktop source refused every Mac. The Gateway now performs
the ARD handshake itself against the loopback server and hands the
browser a plain RFB 003.008 no-auth handshake, so the operator's macOS
account password authenticates the desktop without ever reaching the
browser, the observe result, a URL, or a log.
- rfb-preauth: ARD (type 30) Diffie-Hellman with MD5-derived AES-128-ECB
credentials, and VncAuth (type 2) bit-reversed DES, both under a single
10s negotiation deadline; Apple's RFB 003.889 maps to 3.8
- observe-bridge: runs pre-auth before splicing and starts the view-only
filter at clientInit, since the browser handshake is consumed here;
worker tokens keep the original version start phase
- host-source: attaches ARD, requiring per-observation credentials that
live only in the one-shot observer token and are dropped after use
- doctor: offers an explicitly confirmed sudo launchctl repair when
Screen Sharing is off, and prints the System Settings path otherwise
Live-verified against this Mac's Screen Sharing: the DH exchange and
credential framing are accepted and the server returns SecurityResult.
The VncAuth DES vector is confirmed against OpenSSL independently.