Support the dns-mode field introduced in mihomo v1.19.33. A
button group below the stack selector offers
Auto/Disabled/Native/Hijack; Auto writes an empty string, which
the core treats as the legacy behavior. Adding dns-mode to
GUI_KEYS makes the value GUI-authoritative once saved, matching
the other TUN dialog fields.
Limit GUI authority to saved TUN fields while preserving custom overrides.
Sync provider caches after service startup, reloads and updates.
Publish copies atomically with session, runtime and cache metadata checks.
Each auto-update reloads the core, which drops every connection running through a
`proxy-providers` entry. Existing profiles are corrected once at startup; the form
warns about shorter intervals but still saves them.
* fix(core): reopen PAC when a start attempt never happens
`save_proxy_ports` closes the PAC endpoint before it stages a candidate,
because the endpoint resolves the Mixed Port through the draft layer and
would otherwise answer with a port nothing is listening on.
Most ways out of that function are a rejection, though: the candidate is
refused and the Core keeps serving on its old ports, having never been
stopped. PAC is re-derived only when the Running Mode changes, and that
is exactly what did not happen — so the endpoint stayed shut for the rest
of the session, and every browser on PAC silently went direct.
Give `core_starting` the counterpart it was missing. `core_start_settled`
re-derives PAC from the Running Mode rather than restoring a saved flag,
which makes it correct on all three endings — a start that succeeded, one
that was abandoned, one that failed and left the Core down — and therefore
safe to run from a guard on every path.
* fix(config): refuse overlapping draft transactions instead of losing an edit
A layer holds exactly one draft, so a second writer's `edit_draft`
overwrites the first writer's staged value in place. `DraftTransaction`
nonetheless rolled back by calling `discard` on the shared layer, which
discarded whatever was staged there — including a change another
transaction was about to commit.
The other transaction then found no draft, and `apply` on an absent draft
does nothing at all. It saved the committed value, returned Ok, and its
side effect had already happened: auto-launch registered with the OS while
the config that describes it says otherwise, and no log line either way.
"Roll back only my edit" cannot be implemented over a single slot, so
enforce the exclusivity the type already assumed. Beginning a transaction
claims every layer and refuses rather than interleaving, which is the same
shape as `try_start_config_update` and `begin_operation` elsewhere.
`patch_verge` is the caller that matters: it holds its draft across
`process_terminated_flags`, which restarts the Core and can take seconds.
The TUN reconciliation runs on every Run State transition and can now lose
that race, so it treats a busy layer as "later" rather than as a failure
worth telling the user about.
* fix(core): reconcile TUN availability when the setting itself changes
Moving the TUN availability guard from the frontend into Rust also moved
what triggers it. The old effect depended on `enable_tun_mode`; the new
reconciliation is spawned from `RealEnv::publish`, so it only runs on a
Run State transition. Those two sets are not the same, and the setting
itself falls in the gap.
Switching TUN on from the global hotkey is the case that reaches it. That
path is not gated on availability — unlike the tray item and the settings
switch, both of which read `tun_capable` — and the flags a TUN patch
raises are answered by a Core reload, which changes no Run State at all.
So on a machine with no Service and no elevation the setting stayed on:
tray icon, settings switch and `enable_tun_mode` in verge.yaml all
reported TUN as enabled while nothing carried its traffic, until some
unrelated core lifecycle transition happened to reconcile it.
Reconcile after any patch that touched the setting. The reconciliation
writes configuration itself, so it goes through the plain apply rather
than back through the patch-and-reconcile form; the absence of a cycle is
then a property of the call graph rather than of a runtime early-return.
`toggle_tun_mode` now reads the setting back instead of returning what it
asked for, so the hotkey cannot report TUN as on moments before the notice
saying it was turned off.
* fix(core): an action already under way is not a decision to put to the user
The legacy status could not distinguish a requested privileged operation
from one already running, because readers blocked until the operation
finished and so never observed the request at all. Run State pushes every
transition instead, and `service_needs_attention` treated any pending
action as a question for the user.
So an install started from the settings page or the home card raised the
migration dialog on top of it, and an uninstall raised one offering to
reinstall the Service being removed. Both offer a button that can only
fail, since the privileged-operation slot is already taken.
An operation in flight is a report of progress. The dialog the user is
already driving keeps itself open through its own loading state.
`tun_should_be_disabled` is unaffected: it already refused to act while an
operation was in flight, so the term this adds is redundant there.
* fix(core): let the snapshot decide whether the Run State has settled
`settled` read the operation slot twice: once to decide whether to wait,
then again inside the snapshot it returned. An operation claiming the slot
between those two reads was described rather than waited for.
`prepare_startup` reads this. A start racing an install would see the
requested install, flatten it to `InstallRequired`, and decide `Wait` —
leaving the core stopped where waiting for the install to finish would
have told it whether the Service came back.
Read the slot once, inside the snapshot, so there is no window rather than
a second check closing it. The rewrite that introduced this also fixed a
lost wakeup here, and that fix stands: the waiter is still registered
before the state is read.
* fix(ui): re-read the Run State once its listener is live
Replacing the poll with a push left a gap. `listen` resolves
asynchronously, so between the first `get_runtime_state` and the
subscription being registered there is a window, and a transition landing
in it is not queued anywhere — it is simply missed. The next event is
whenever the backend happens to move again, which during startup can be a
long time; the 30-second safety net was all that stood behind it, where
the old code polled every two seconds precisely through startup.
The Run State is the one thing here that arrives only by event, so it is
the one thing a missed event leaves stale. Re-read it once every listener
is live, which closes the gap without going back to polling for it.
`onSubscribed` fires even when a registration failed, because a resync is
most needed when a subscription is missing, and not at all after teardown.
* docs(config): state what makes the PAC endpoint's draft read safe
`MixedPort::desired` reads the draft layer, so the PAC endpoint can name a
port the Core has not moved to yet. That is only safe because the one path
staging a listener port closes PAC across the change. Nothing said so at
either end, and the two are in different modules.
* fix(core): ask again when staging goes silent rather than assume it failed
The Service commits the generation before it answers, so a reply that never
arrives is not evidence that nothing was written: config.yaml can already hold
the new configuration while this side concludes the staging never happened. The
Core keeps running the previous configuration, so nothing looks wrong -- until
the Core dies and the Service's watchdog restarts it in the same generation from
whatever config.yaml says, quietly making live a configuration the app reported
as failed.
Silence is the one answer that has to be resolved rather than acted on, so ask
once more before falling back. It is cheap exactly where it matters: staging
plans against the manifest it wrote, so a bundle that already landed copies
nothing and answers in milliseconds, and the Service's lifecycle lock makes a
second ask wait for a first one still in flight instead of interleaving with it.
A deadline keeps that wait off the caller, and a second silence goes where the
first one used to.
* feat(service): isolate macOS launch and owner runtime
* ci(windows): run owner security tests
* chore(deps): use kode-bridge v0.4.1 tag
* fix(macos): classify installed service state
* fix(macos): classify marked unavailable service
* fix(macos): gate core startup on service migration
* fix(macos): scope service init to supported platforms
* feat(macos): prompt before migrating installed service
* fix(macos): shorten sidecar IPC path
* build(service): pin helper protocol and artifacts
* fix(core): require confirmed service and sidecar readiness
* fix(macos): harden launch roots and singleton state
* fix(ui): refresh migration state before restart
* fix(macos): linearize service migration lifecycle
* fix(ui): keep service migration state fresh
* feat(service): track authenticated owner sessions
* fix(service): preserve newer owner sessions
* feat(macos): route service proxy changes through helper
* fix(macos): serialize service proxy guard
* fix(macos): close service proxy owner lifecycle
* fix(macos): close remaining proxy stop races
* fix(app): do not cancel controlled service stop
* fix(lifecycle): keep controlled updates cancellation-safe
* fix(macos): stage dev core for service
* test(macos): exercise staged core cleanup
* fix(dev): await controlled macOS exit
* chore: update changelog
* fix(macos): recover sidecar after service install failure
Allow the existing sidecar to continue when service installation leaves the manager in InstallRequired, then settle the session state as SidecarAllowed. Show neutral status text while the migration dialog waits for its final state.
* fix(macos): stage dev service tools before elevation
* fix(macos): localize launch guard prompts
* fix(service): address cross-platform integration issues
* feat: support seamless service migration
* fix: pass Windows service installer path safely
* fix(prebuild): derive service release from Cargo dependency
* fix(dev): preserve service installation state
* fix: resolve clippy warnings in service lifecycle
Wait longer for slow Windows service startup, then hand off from sidecar
to service when it becomes ready. Retry service core startup during the
handoff window, and clean up root-owned macOS service core/IPC leftovers
before uninstalling the service.
* fix: 修复macos重启应用后需要重设服务器模式问题
* chore: remove package-lock.json (using pnpm)
* Delete test.sh
* refactor(lifecycle): remove unnecessary conditional compilation for macOS and Windows
* refactor(timing): remove conditional compilation for service wait durations on Windows and macOS
---------
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
* Refactor proxy management: Remove EventDrivenProxyManager and async_proxy_query
- Removed the EventDrivenProxyManager and its related event-driven proxy management logic.
- Replaced usages of AsyncProxyQuery with direct calls to sysproxy for fetching system and auto proxy configurations.
- Cleaned up constants by removing unused default proxy host.
- Updated sysopt to eliminate calls to the removed EventDrivenProxyManager.
- Adjusted logging and proxy state management to reflect the removal of event-driven architecture.
- Simplified the core module structure by removing unnecessary module imports.
* refactor: remove bypass module for cleaner network configuration
* feat: update sysproxy to version 0.4.0 and add guard feature; refactor sysopt for improved proxy management
* feat(windows/sysproxy): unify guard-type handling and auto-restore drift
* refactor(config): remove commented-out code for SysProxy update flag
---------
Co-authored-by: Slinetrac <realakayuki@gmail.com>
* refactor: replace hardcoded DNS config filename with constant reference
* refactor: remove redundant import of constants in IClashTemp template method
* refactor: add conditional compilation for DEFAULT_REDIR based on OS
* refactor: simplify default TPROXY port handling and remove unused trace_err macro
* refactor: simplify default TPROXY port fallback logic