Skip to content

Releases: MasterAlanLab/avalon

v1.0.7 -- "生命中,再无聊的时光,也都是限量版。"

Choose a tag to compare

@github-actions github-actions released this 04 Sep 07:08
  • chore(release): bump version to 1.0.7

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01GXxfHB38J4dvd7j4vEnWGA

  • refactor: strip dead code and the redundant CoreInterface layer

  • An ablation pass over lib/ (core/ excluded): remove each candidate, then

  • require flutter analyze and flutter test to stay at baseline.

  • CoreInterface was a mixin declaring 35 abstract methods that

  • CoreHandlerInterface then implemented one for one, and nothing in the tree

  • ever used it as a type. Merged it into CoreHandlerInterface, keeping the

  • four lifecycle methods the platform backends supply.

  • Also gone: two files nothing imported (common/archive.dart, common/store.dart),

  • 27 unreferenced top-level declarations, and ~25 dead members. Three of those

  • were hiding defects rather than just weight -- putProfileDisabledRule wrote

  • RuleScene.added instead of disabled, Script.saveWithPath copied a file onto

  • itself, and importUri was a bare alias for importText. Dropping

  • getMaterialColorShades made _createPrimarySwatch, value32bit, the four *8bit

  • getters and lighten unreachable; dropping Windows.registerTask left

  • String.encodeUtf16LeWithBom with no caller but its own test, so that pair went

  • too (hence 862 tests, down from 864).

  • Kept, because the ablation showed they carry weight: the six abstract

  • interfaces under core/desktop each pair a production implementation with a

  • test fake, and NodeCodec has eight implementations.

  • Verified beyond the suite by installing a debug APK on an arm64 emulator --

  • core start/stop/restart, theme switching, node import and the settings screens

  • all behave as they did before.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01XSQDyYNbJPMa7e3Bkxhgrt

Release Downloads

Download based on your OS:

OS Download
Android

Windows
macOS
Linux

v1.0.6 -- "到最后遗憾的是我,还是你。"

Choose a tag to compare

@github-actions github-actions released this 03 Sep 11:02
  • fix(ci): survive the flaky hdiutil detach that fails the macOS dmg

  • The v1.0.6 build produced Avalon.app fine and then died in appdmg's last

  • steps: hdiutil detach /Volumes/Avalon returned "No such file or directory",

  • appdmg treated that as fatal and deleted both the temporary image and the

  • target dmg. Twelve seconds passed between "Blessing image" and the failure,

  • so appdmg had been retrying: the first detach reported an error, the volume

  • went away underneath it anyway, and every retry after that found nothing left

  • to unmount. The image was complete the whole time.

  • This is the runner, not the build - a volume held a moment too long by a

  • background process, or unmounted asynchronously by DiskArbitration. appdmg

  • gives us no way to tell it that an already-unmounted volume is a success, so

  • put a shim in front of hdiutil on the PATH of the packaging process. It only

  • touches detach: an absent mount point exits 0, a busy one is retried, and

  • the last attempts sync and force it out so the image content is still

  • flushed. Five failed attempts still exit non-zero, so a genuinely stuck

  • volume is not swallowed.

  • The shim is only prepended for macOS packaging, so no other platform's

  • environment changes.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_013ChdTt9BG4wAfqY35MDr1o

  • chore(release): bump version to 1.0.6

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01NSAj59bHG8PDrWLncUW8fC

  • fix(nodes): default udp on, the real cause of the WebRTC leak

  • The virtual adapter was never the problem. With the tun up and its routes

  • installed, a STUN probe still left through the physical interface carrying the

  • real address, and 1.0.4's strict-route did nothing about it.

  • mihomo gates UDP on a per-outbound flag: adapter/outbound/*.go declares it as

  • UDP bool with the proxy:"udp,omitempty" tag, so a proxy that omits the key

  • is parsed as not supporting UDP. tunnel.go's match() then skips every rule that

  • resolves to such an outbound and, once the rules run out, returns

  • proxies["DIRECT"]. The packet leaves through the physical interface with the

  • real source address and nothing is logged. WebRTC's STUN probes and QUIC both

  • take that path, which is why ipcheck.ing kept reporting the real address while

  • everything else went through the proxy.

  • Share links do not carry udp; only Clash YAML subscriptions write it. So this

  • hit exactly the nodes this app exists for - the ones imported by URI and

  • combined into relay chains - and left subscription users untouched.

  • Default it to true, in the URI codec so the value is visible in the node editor

  • and travels with exported links, and again in effectiveNodeConfig and

  • effectiveStoredNodeConfig so nodes already in the library are covered without a

  • migration. Profile proxies get the same treatment in the chain runtime: a

  • subscription that omits the key leaks the same way and never reaches the node

  • library. Only the fifteen types the core actually reads the key for; hysteria,

  • hysteria2, tuic and shadowquic set UDP in Base themselves, http and ssh are

  • always false. The default is applied before the overlay merge, so an explicit

  • udp: false and an explicit removal both still win.

  • A server that cannot carry UDP now fails to dial instead of quietly going

  • direct, which is the failure this needs.

  • Drop the plain udp flag from the relay-hop compatibility check. It is the

  • default now and carries no signal; leaving it in would have put a warning on

  • every multi-hop chain, and setup.dart logs each one on every apply.

  • Hide the strict-route switch on macOS. sing-tun implements it in tun_linux.go

  • as unreachable rules and in tun_windows.go as WFP filters; tun_darwin.go never

  • reads the field. The switch and its description promised a protection that does

  • not exist there. The field, the bridge and the config writer stay - they are

  • real on the other two desktops.

  • Turn the tun switch back off when authorization fails. The state stayed at

  • unauthorized, _getEffectiveTunEnable rewrote tun.enable to false for the core,

  • and the switch kept showing on: the user believes traffic is going through the

  • virtual adapter while only the system proxy is running. Log it too, because the

  • state was otherwise invisible from outside the process. Authorization is still

  • asked for once per app lifecycle.

  • Stop escaping spaces in checkIsAdmin's core path. It goes to Process.run as an

  • argv element, not through a shell, so an escaped path makes stat miss the file

  • and the app reports itself as unauthorized.

  • Verified on device: importing a share link without a udp parameter now stores

  • udp: true, while nodes imported before this change still have the key absent.

  • For anyone reading the 1.0.4 notes: "stop DNS and WebRTC leaking the real

  • address" was wrong about WebRTC. strict-route is a no-op on macOS and the leak

  • had a different cause entirely. The IPv6 takeover and fake-ip-range6 changes

  • from that release are unrelated and still stand.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01NSAj59bHG8PDrWLncUW8fC

Release Downloads

Download based on your OS:

OS Download
Android

Windows
macOS
Linux

v1.0.5 -- "就像那句狠话:有一天,我一定会后悔的。"

Choose a tag to compare

@github-actions github-actions released this 03 Sep 03:21
  • chore(release): bump version to 1.0.5

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01LKYUwNq8JMjiGfvPQWekST

  • fix(nodes): send XHTTP transport settings to xhttp-opts

  • Importing a VLESS or VMess link with type=xhttp put path and host into

  • ws-opts. mihomo reads XHTTP settings from xhttp-opts only (XHTTPOptions

  • in adapter/outbound/vless.go), so path was lost, mode was dropped on

  • the floor, and host fell back to the TLS servername. The node could not

  • connect at all, and nothing in the app ever wrote xhttp-opts, so only

  • raw YAML nodes could carry an XHTTP transport.

  • Route them to xhttp-opts on import, keep mode, and read them back on

  • export so the URI round-trips. httpupgrade keeps using ws-opts, which

  • is where mihomo reads it from.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01LKYUwNq8JMjiGfvPQWekST

  • fix(tun): put the IPv6 takeover behind an explicit switch

  • The virtual adapter took over IPv6 whenever it was enabled, overriding

  • the IPv6 switch the user had turned off in settings. That also handed

  • ::/0 to the tun, so direct traffic started depending on the host having

  • a working IPv6 uplink — the shape of failure where a half-broken v6

  • (address and default route present, packets dropped) makes dual-stack

  • sites time out.

  • Add tun.ipv6, on by default, and let it decide. With it off,

  • inet6-address is not written either, so sing-tun never installs the

  • ::/0 route, and the global ipv6 switch is left exactly as the user set

  • it. Taking over ::/0 is safe to keep as the default: verified on a

  • device with deliberately broken IPv6 that domestic sites still load,

  • because mihomo restores the domain in preHandleMetadata and re-resolves

  • it, so the outbound address family is mihomo's choice and

  • tcp-concurrent races the dead v6 leg away. Xray honours what the client

  • picked and has no such fallback, which is why the same setup breaks

  • there.

  • Android builds the adapter in VpnService, where tun.enable is always

  • false, so the takeover lives in the VPN IPv6 switch instead. That

  • switch never reached the core: VpnService pointed ::/0 at the adapter

  • while the core still had ipv6 off, so the route was taken over but no

  • AAAA was ever resolved. Both platforms now decide through

  • SetupAction._tunTakesIpv6, and the result is passed into

  • makeRealProfileTask rather than derived there — that runs in a compute

  • isolate and should not reach for the platform singleton.

  • Also move the fake-ip-range6 gate out of the overrideDns branch. A

  • profile shipping its own dns block skips that branch entirely, so a

  • subscription setting ipv6: true silently overrode the local DNS IPv6

  • switch. Outside the branch the dns map may still be the read-only one

  • the profile arrived with, so copy it before writing.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01LKYUwNq8JMjiGfvPQWekST

  • chore(release): bump version to 1.0.4

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn

  • fix(tun): stop DNS and WebRTC leaking the real address

  • With the virtual adapter on, DNS queries and WebRTC candidates still

  • exposed the real address. mihomo implements the defences for both, but

  • the app could not reach them: the Tun model, the config writer and the

  • Go bridge each carried the same six tun fields, so strict-route had no

  • way out of the app.

  • Add strict-route, on by default. On Windows sing-tun installs WFP

  • filters that permit only the tun interface and this process and block

  • remote port 53 everywhere else, which is what Windows' multihomed name

  • resolution needs; on Linux it adds the unreachable ip rules.

  • Make the tun carry IPv6. mihomo clears tun.inet6-address whenever ipv6

  • is false (config.parseIPV6), so a tun that does not take over IPv6

  • leaves the system routing it out of the physical interface. Force ipv6

  • on whenever the tun is enabled and write inet6-address explicitly.

  • Whether AAAA is resolved stays with dns.ipv6, so this does not change

  • which address family applications prefer.

  • Both settings also travel through the bridge, because core_manager

  • patches the running config instead of rewriting the profile: enabling

  • the tun after launch would otherwise recreate it without the IPv6

  • address it needs.

  • Add dns.fake-ip-range6 and tie it to dns.ipv6. Under fake-ip, AAAA is

  • answered by the withFakeIP middleware, which returns an empty answer

  • when there is no v6 pool and never reaches the dns.ipv6 check in

  • withResolver, so that switch did nothing in the default mode. It now

  • provisions the pool. Hosts without global IPv6 are unaffected:

  • parseIPV6 runs before parseDNS and parseTun and clears both the v6 pool

  • and the tun address.

  • Verified by parsing the generated config with mihomo's own config.Parse

  • in both environments. With IPv6 the tun keeps fdfe:dcba:9876::1/126 and

  • FakeIPPool6 is created; without it both are dropped and parsing still

  • succeeds.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn

  • chore(release): bump version to 1.0.3

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • feat(profiles): give each tab its own action button

  • The Profiles scaffold showed one Add Profile button for all three tabs,

  • while the node and chain actions sat inline above their lists. The

  • button that matters depends on the tab you are looking at.

  • Drive the floating action button from the tab index: Add Profile on

  • Profiles, Create node and Import node on Nodes, Create chain on Chains.

  • The inline buttons in the node toolbar and above the chain list are gone

  • now that the actions live in the scaffold; the empty states keep theirs

  • as a call to action.

  • The two node buttons sit at opposite edges, which the end-aligned slot

  • cannot express, so CommonScaffold gained a floatingActionButtonLocation

  • and that tab uses centerFloat with the edge inset added back.

  • To reach the actions from the scaffold, the node import/create handlers

  • and the chain create handler become top-level functions in their own

  • files; the views delegate to them.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • feat(nodes): import nodes without a preview step

  • Removing only the overwrite-or-copy radios left the preview dialog in

  • place, so importing still interrupted the user to confirm a list they

  • did not ask to review. Importing a link means "add this": parse it, add

  • what is valid, and report what is not.

  • Drop the preview entirely from both import paths. NodeLibraryService

  • already skips drafts that fail to parse and returns their issues, so the

  • Nodes tab now surfaces those issues instead of showing them in a list

  • nobody needed to approve. Binding follows the caller as before: imports

  • bind to the current profile when there is one.

  • Delete NodeImportPreview, NodeImportSelection and their test, along with

  • the importPreview and bindCurrentProfile messages that only that dialog

  • used. _previewAndCommitNodes becomes _commitNodes, since it no longer

  • previews anything.

  • Verified on an Android emulator: a node deep link and the Nodes tab

  • import both create the node with no dialog, and importing the same link

  • twice yields two copies.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • feat(nodes): always add on import instead of asking about duplicates

  • The import preview asked the user to choose between updating the

  • existing node and creating a copy. The choice is not worth a prompt:

  • importing a link means "add this", and re-importing the same link should

  • simply yield another copy.

  • Drop the radio group and commit imports with createCopy, so the preview

  • only asks what it still needs, which drafts to keep and whether to bind

  • them to the current profile.

  • Clean up what the choice left behind:

    • NodeImportSelection.createCopy, now that nothing reads it.
    • The createCopy parameter on importNodeText and _previewAndCommitNodes,
  • which no caller ever passed.

    • The updateExisting message, whose only reference was the removed radio.
  • NodeLibraryService.commit keeps its createCopy parameter: the node menu

  • still duplicates a node with it, and manual creation still dedupes by

  • fingerprint without it.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • fix(android): keep warm-start deep links out of Flutter routing

  • Deep links are handled by app_links, but the Flutter engine's own deep

  • link handling is on by default and pushes the incoming URI into the

  • navigation system. This app uses a plain MaterialApp with no route

  • generator, so a link delivered to an already running instance raised

  • "Could not find a generator for route" and never reached LinkManager.

  • Cold start worked only because getInitialLink reads the intent

  • directly. Warm start is the common case here, since the app is meant to

  • stay running in the background while a subscription link is opened from

  • a browser or chat app.

  • Set flutter_deeplinking_enabled to false so the intent reaches

  • app_links in both cases.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • fix(nodes): treat every http(s) URI as a subscription

  • isHttpProxyUri classified any http(s) URI with an explicit port as an

  • HTTP proxy node, so...

Read more

v1.0.4 -- "那些过去,从未过去。"

Choose a tag to compare

@github-actions github-actions released this 03 Sep 01:39
  • chore(release): bump version to 1.0.4

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn

  • fix(tun): stop DNS and WebRTC leaking the real address

  • With the virtual adapter on, DNS queries and WebRTC candidates still

  • exposed the real address. mihomo implements the defences for both, but

  • the app could not reach them: the Tun model, the config writer and the

  • Go bridge each carried the same six tun fields, so strict-route had no

  • way out of the app.

  • Add strict-route, on by default. On Windows sing-tun installs WFP

  • filters that permit only the tun interface and this process and block

  • remote port 53 everywhere else, which is what Windows' multihomed name

  • resolution needs; on Linux it adds the unreachable ip rules.

  • Make the tun carry IPv6. mihomo clears tun.inet6-address whenever ipv6

  • is false (config.parseIPV6), so a tun that does not take over IPv6

  • leaves the system routing it out of the physical interface. Force ipv6

  • on whenever the tun is enabled and write inet6-address explicitly.

  • Whether AAAA is resolved stays with dns.ipv6, so this does not change

  • which address family applications prefer.

  • Both settings also travel through the bridge, because core_manager

  • patches the running config instead of rewriting the profile: enabling

  • the tun after launch would otherwise recreate it without the IPv6

  • address it needs.

  • Add dns.fake-ip-range6 and tie it to dns.ipv6. Under fake-ip, AAAA is

  • answered by the withFakeIP middleware, which returns an empty answer

  • when there is no v6 pool and never reaches the dns.ipv6 check in

  • withResolver, so that switch did nothing in the default mode. It now

  • provisions the pool. Hosts without global IPv6 are unaffected:

  • parseIPV6 runs before parseDNS and parseTun and clears both the v6 pool

  • and the tun address.

  • Verified by parsing the generated config with mihomo's own config.Parse

  • in both environments. With IPv6 the tun keeps fdfe:dcba:9876::1/126 and

  • FakeIPPool6 is created; without it both are dropped and parsing still

  • succeeds.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn

Release Downloads

Download based on your OS:

OS Download
Android

Windows
macOS
Linux

v1.0.3 -- "宇宙是蚂蚁的梦。"

Choose a tag to compare

@github-actions github-actions released this 02 Sep 17:56
  • chore(release): bump version to 1.0.3

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • feat(profiles): give each tab its own action button

  • The Profiles scaffold showed one Add Profile button for all three tabs,

  • while the node and chain actions sat inline above their lists. The

  • button that matters depends on the tab you are looking at.

  • Drive the floating action button from the tab index: Add Profile on

  • Profiles, Create node and Import node on Nodes, Create chain on Chains.

  • The inline buttons in the node toolbar and above the chain list are gone

  • now that the actions live in the scaffold; the empty states keep theirs

  • as a call to action.

  • The two node buttons sit at opposite edges, which the end-aligned slot

  • cannot express, so CommonScaffold gained a floatingActionButtonLocation

  • and that tab uses centerFloat with the edge inset added back.

  • To reach the actions from the scaffold, the node import/create handlers

  • and the chain create handler become top-level functions in their own

  • files; the views delegate to them.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • feat(nodes): import nodes without a preview step

  • Removing only the overwrite-or-copy radios left the preview dialog in

  • place, so importing still interrupted the user to confirm a list they

  • did not ask to review. Importing a link means "add this": parse it, add

  • what is valid, and report what is not.

  • Drop the preview entirely from both import paths. NodeLibraryService

  • already skips drafts that fail to parse and returns their issues, so the

  • Nodes tab now surfaces those issues instead of showing them in a list

  • nobody needed to approve. Binding follows the caller as before: imports

  • bind to the current profile when there is one.

  • Delete NodeImportPreview, NodeImportSelection and their test, along with

  • the importPreview and bindCurrentProfile messages that only that dialog

  • used. _previewAndCommitNodes becomes _commitNodes, since it no longer

  • previews anything.

  • Verified on an Android emulator: a node deep link and the Nodes tab

  • import both create the node with no dialog, and importing the same link

  • twice yields two copies.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • feat(nodes): always add on import instead of asking about duplicates

  • The import preview asked the user to choose between updating the

  • existing node and creating a copy. The choice is not worth a prompt:

  • importing a link means "add this", and re-importing the same link should

  • simply yield another copy.

  • Drop the radio group and commit imports with createCopy, so the preview

  • only asks what it still needs, which drafts to keep and whether to bind

  • them to the current profile.

  • Clean up what the choice left behind:

    • NodeImportSelection.createCopy, now that nothing reads it.
    • The createCopy parameter on importNodeText and _previewAndCommitNodes,
  • which no caller ever passed.

    • The updateExisting message, whose only reference was the removed radio.
  • NodeLibraryService.commit keeps its createCopy parameter: the node menu

  • still duplicates a node with it, and manual creation still dedupes by

  • fingerprint without it.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • fix(android): keep warm-start deep links out of Flutter routing

  • Deep links are handled by app_links, but the Flutter engine's own deep

  • link handling is on by default and pushes the incoming URI into the

  • navigation system. This app uses a plain MaterialApp with no route

  • generator, so a link delivered to an already running instance raised

  • "Could not find a generator for route" and never reached LinkManager.

  • Cold start worked only because getInitialLink reads the intent

  • directly. Warm start is the common case here, since the app is meant to

  • stay running in the background while a subscription link is opened from

  • a browser or chat app.

  • Set flutter_deeplinking_enabled to false so the intent reaches

  • app_links in both cases.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • fix(nodes): treat every http(s) URI as a subscription

  • isHttpProxyUri classified any http(s) URI with an explicit port as an

  • HTTP proxy node, so a subscription link served on a non-default port

  • was imported as a node instead of creating a profile. Self-hosted

  • panels commonly use ports such as 8443 or 2096, and the

  • avalon://install-config deep link routes through the same path, so its

  • explicit intent was overridden by the heuristic.

  • http and https are no longer node schemes, matching what the deep link

  • dispatcher and the Android intent filters already assumed. Drop

  • HttpCodec, its https alias and the port heuristic, and let importText

  • treat any http(s) URI as a subscription URL.

  • Raw Mihomo configs carrying type: http proxies keep working; only the

  • URI codec is gone.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • perf(telemetry): gate UI telemetry on a unified activity state

  • Traffic sampling ran on a fixed 1s global timer that only stopped when

  • the proxy stopped, so a hidden window still paid for Dart scheduling,

  • two IPC round trips and provider updates every second.

  • Introduce a process-wide AppActivityState fed by the Flutter lifecycle,

  • window visibility and window focus, and drive telemetry from it:

    • SetupAction picks 1s, 5s or no sampling from the current activity,
  • the dashboard traffic widgets and the tray title need, resamples

  • immediately on resume, and skips a tick while a request is in flight.

    • The tray speed title is macOS-only, so its need is platform-gated;
  • otherwise showTrayTitle (default true) kept background sampling alive

  • for a consumer that does not exist off macOS.

    • Pages and the app root wrap their subtree in TickerMode so animations
  • stop when the page or the window is inactive.

    • NetworkSpeed and TrafficUsage drop their provider subscription while
  • inactive; Logs and Requests buffer through ActiveSnapshotMixin and

  • commit one snapshot once page and window are both active again.

    • Tray titles dedupe on the formatted string, and getTraffic /
  • getTotalTraffic bypass the debug RPC timing logs.

  • Frame pausing now distinguishes an on-screen window from a hidden one:

  • blur only gates telemetry and tickers, while hide and minimize pause

  • frames after 300ms instead of 5s. Losing focus must not stop frames, as

  • an unfocused window is still scrollable and would appear frozen.

  • Verified on an Android emulator: 1 tick/s in the foreground, zero ticks

  • across 25s backgrounded, and an immediate resample on resume.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq

  • fix(ui): show chain names and align node actions

Release Downloads

Download based on your OS:

...
OS Download
Android

Windows
macOS
Linux

Read more

v1.0.2 -- "经过上千年的修炼,你学到的就是这个吗?"

Choose a tag to compare

@github-actions github-actions released this 01 Sep 14:13
  • ci: retry Flutter SDK setup after transient downloads

  • ci: use Maven Central mirror for Android builds

  • chore(release): bump version to 1.0.2

  • Start Flutter splash from a blank first frame

  • Add animated Flutter startup splash

  • Refresh primary icon and status generator

  • Update Avalon branding assets across platforms

Release Downloads

Download based on your OS:

OS Download
Android

Windows
macOS
Linux

v1.0.1 -- "很多人宁可否认事实,也不愿面对真相。"

Choose a tag to compare

@github-actions github-actions released this 01 Sep 02:33
  • chore(release): prepare signed v1.0.1

  • ci: keep release notes out of the repository

Release Downloads

Download based on your OS:

OS Download
Android

Windows
macOS
Linux

v1.0.0 -- "无心忆本失心意,忆心失本心无意。"

Choose a tag to compare

@github-actions github-actions released this 01 Sep 01:48
  • fix(windows): resolve installer locale path

  • ci: drop unsupported build architectures

  • chore: organize branding sources

  • chore(release): bump version to 1.0.0

  • ci: optimize release workflow

  • feat: refresh cross-platform branding assets

  • fix: update project links and About page

  • fix: correct packaging paths and platform naming

  • docs: rewrite README and sync translations

  • chore: license Avalon additions under AGPLv3

  • fix(android): update JNI exports for Avalon package

  • refactor: drop the dead crash-reporting plumbing and unused strings

  • Removing Firebase left a settings field, a shared-state field and four

  • localized strings behind, kept alive only by a localization test. This drops

  • them end to end: AppSettingProps.crashlytics/crashlyticsTip, SharedState.crashlytics

  • on both the Dart and Kotlin sides, and the crashlytics, crashlyticsTip,

  • dataCollectionTip and dataCollectionContent strings in all four locales. The

  • shared-state provider now watches two fields instead of three.

  • Also removes chainLibrary, importNodeDesc and chainHopHint, three strings that

  • were never referenced, and renames plugins/rust_api/CLAUDE.md to NOTES.md so the

  • flutter_rust_bridge integration notes survive without carrying an assistant

  • config filename.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • chore: remove the remaining agent tooling files

  • AGENTS.md and CLAUDE.md only routed into the deleted .agents directory, and

  • .mcp.json and opencode.jsonc configured a codegraph MCP server this project

  • no longer uses.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • chore: remove the JetBrains run configuration

  • .run/main.dart.run.xml only provides a ready-made Flutter run entry for

  • JetBrains IDEs and takes no part in building, testing or packaging.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • chore: stop ignoring docs/

  • The directory now holds the translated READMEs, so new files under it must

  • be trackable. Also drops the .claude/settings.local.json rule, which no

  • longer has a directory to apply to.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • docs: move the translated READMEs under docs/

  • Keeps the Chinese README at the repository root and relocates the seven

  • translations to docs/. Language navigation, image sources and the LICENSE,

  • NOTICE and CHANGELOG links are rewritten so every relative path still

  • resolves from its new depth.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • chore: remove the .codegraph directory

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • chore: remove agent tooling configs

  • Drops the .gemini, .codex, .claude, .agents and .zed directories, which

  • carry per-tool assistant configuration rather than anything the project

  • builds or ships.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • chore(release): bump version to 0.8.97

  • Artifact file names come from the pubspec version while the release body

  • template builds its download links from the tag, so the two have to match.

  • v0.8.96 is already taken by an upstream FlClash tag, hence 0.8.97 for

  • Avalon's first release.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • ci: put a hitokoto quote in the release title

  • Every release is named <tag> -- "<quote>", matching how emailbox names

  • its releases. The quote is fetched from v1.hitokoto.cn with a 5s timeout and

  • falls back to a fixed string when the API is unreachable or returns something

  • jq cannot parse. The value is stripped of newlines and quotes and capped at 80

  • characters so remote content cannot inject extra step outputs or break the

  • title.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • refactor!: rename FlClash fork to Avalon and trim the release pipeline

  • Rebrand this FlClash fork as Avalon, repoint every project-owned identifier,

  • and reduce the inherited release pipeline to building installers and attaching

  • them to a GitHub Release.

  • Rename:

    • Dart package fl_clash -> avalon
    • application id com.follow.clash -> com.masteralanlab.avalon, with the Kotlin
  • package directories moved to match

    • executables FlClashCore / FlClashHelperService -> AvalonCore /
  • AvalonHelperService, keeping the Windows pipe name, unix socket path and

  • helper protocol header in sync between the Dart and Rust sides

    • custom URL scheme flclash:// -> avalon:// and generated chain group prefix
  • _flclash -> _avalon

    • a fresh Inno Setup app id so the Windows installer is a distinct application
  • Upstream references are deliberately left untouched: the Clash.Meta submodule

  • branch, the flutter_distributor git ref and the chen08209 forks in pubspec.yaml

  • still point at the upstream projects.

  • Docs:

    • rewrite the README for Avalon in 8 languages, replacing README_zh_CN.md
    • add NOTICE carrying the GPL-3.0 attribution to FlClash and the list of
  • modifications made in this fork

    • state in every README that the project is for study and research only and
  • that any illegal use is prohibited, and add a recommended-resources section

  • CI:

    • drop the F-Droid, Homebrew tap and Telegram publishing steps
    • skip Android signing when no keystore secret is configured instead of writing
  • an empty keystore over the build

    • export ANDROID_NDK from the setup-ndk output, which the Go core build tool
  • requires under that exact name

    • fall back to the runner default Xcode when the pinned version is absent
  • Android:

    • remove Firebase Crashlytics and Analytics; crash-loop protection now reads
  • ActivityManager.getHistoricalProcessExitReasons on API 30+ and degrades to

  • no protection below it

  • BREAKING CHANGE: the application identifiers changed, so Avalon installs

  • alongside FlClash instead of upgrading it. Settings, profiles and the WebDAV

  • backup directory are not migrated.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm

  • Fix three codec outputs the Core rejects

  • Device testing surfaced three configs that the Dart layer considered valid

  • but the bundled Core refuses at apply time:

    • SOCKS nodes were emitted as type: socks. Mihomo registers socks5
  • only, so adapter.ParseProxy failed with "unsupport proxy type: socks"

  • and no SOCKS node could start. SOCKS4/4a keep their variant in

  • version, which _socksScheme already uses to restore the scheme.

    • scheme://base64(user:password)@host share links kept the undecoded
  • blob as the username and left the password empty, so servers rejected

  • the auth. Decode it in a shared _credentialParts, but only when the

  • decoded value carries a separator, so a literal username that happens

  • to be valid base64 is left alone. SOCKS and HTTP shared the bug.

    • h2 transport was written to http-opts, and header values were
  • written as plain strings. Mihomo splits these: HTTPOptions takes

  • path as a list and headers values as lists, HTTP2Options takes

  • host as a list with a plain path, and WSOptions takes plain

  • strings throughout. Fixed on URI parse, VMess JSON parse and export.

  • None of these were caught by the existing suite because it validates only

  • within Dart. Two of them were in fact pinned as expected behaviour by

  • codec tests asserting type == 'socks'. Those assertions are corrected

  • here, and hub_validate_test.go now pins the SOCKS naming contract on the

  • Go side.

  • Found by feeding parsed sample URIs for every supported protocol to the

  • real handleValidateConfig; the other 15 protocol/transport combinations

  • tested clean.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01GuY5bCnM5DDp2Qb5LHKCkS

  • Replace the node import entry with create-from-chain

  • Add Profile offered "Import node", which duplicated the button already on

  • the Nodes tab, while there was no way at all to create a profile without a

  • subscription URL or a config file. Nodes and chains are global, but binding

  • either one needs a profile, so the no-subscription single-node flow was

  • unreachable from the UI.

  • Add Profile now lists the chain library instead: picking a chain creates a

  • stub profile, binds the chain as default and sets the chain entry group.

  • The stub ships an empty PROXY group on purpose. _attachChainEntry appends

  • the generated selector and keeps the first member in place, so an empty

  • group leaves the chain as the only member and therefore the default

  • outbound; a DIRECT placeholder would silently win instead. Core validation

  • accepts a select group with no members, verified against the bundled Core.

  • Verified on an Android emulator: the entry lists the chain library, creates

  • the profile and makes it current.

  • Co-Authored-By: Claude Opus 5 noreply@anthropic.com

  • Claude-Session: https://claude.ai/code/session_01GuY5bCnM5DDp2Qb5LHKCkS

  • Fix R1-R9 from the node library and chain review

    • R1: keep old files, database and settings on restore failure
    • R2: preserve SOCKS4/4a version across export round-trip
    • R3: include chains and generated groups in export
    • R4: show diagnostics and confirmation be...
Read more