LocalSend AI 协作文档解析:从 CLAUDE.md 看跨语言 Monorepo 的 Agent 开发规范
LocalSend 是一个「Flutter 应用 + Rust 协议实现」的跨平台局域网文件传输项目。仓库根目录的 CLAUDE.md 是面向 LLM/AI 代理的入口指引文件,它本身极短,却定下了两条最重要的工作约定:其一,任何 Dart/Flutter 操作都必须走 fvm flutter / fvm dart,严禁使用系统级的 flutter / dart 二进制;其二,该仓库的完整代理指南位于 AGENTS.md(CLAUDE.md 通过 @AGENTS.md 引用将其整体纳入上下文)。本文以 CLAUDE.md 为骨架,结合 AGENTS.md 的完整约定与仓库中的真实配置文件、Cargo workspace、CI 工作流,完整还原在这套跨语言 Monorepo 中正确开发、构建与验证的代码路径,适合准备为该仓库贡献代码或研究「如何为 AI Agent 编写仓库级指南」的读者。
一、为什么必须使用 fvm:Flutter 版本锁定机制
CLAUDE.md 的第一条规则:
Always use
fvm flutter/fvm dart, never the bareflutter/dartbinaries — this repo pins its Flutter version in.fvmrcand the system-wide toolchain will not match it.
其背后是一套四处的 Flutter 版本锁定体系(见 AGENTS.md 的 "Flutter version" 一节):
| 位置 | 作用 | 当前仓库中的实际值 |
|---|---|---|
| .fvmrc | fvm 的本地版本锁文件 | {"flutter": "3.41.9"} |
| .github/workflows/ci.yml | CI 环境锁定的 FLUTTER_VERSION |
"3.41.9" |
| app/pubspec.yaml | Dart/Flutter SDK 约束 | flutter: ^3.41.0 |
support/submodules/flutter |
以 git submodule 方式 vendored 的 Flutter SDK | checkout 在 3.41.9 |
四个位置必须保持一致;AGENTS.md 明确指出,升级版本需要同步更新全部四处,具体步骤写在 CONTRIBUTING.md 的 "Bump Flutter" 小节(fvm use 新版本 → 更新 submodule 并 git add → 改 CI 与 pubspec 约束)。
同理,Rust 工具链也通过仓库根目录的 rust-toolchain.toml 锁定为 channel = "1.97.1"(并附带 clippy 组件),保证 cargo 子命令在任何机器上取到相同的工具链。
二、仓库布局:四 Rust crate + 三 Dart 包的 Monorepo
AGENTS.md 给出的目录职责表是理解整个仓库的地图:
| 路径 | 内容 |
|---|---|
| app/ | Flutter 应用 localsend_app:UI、providers、持久化、平台 channel |
| packages/localsend_isolates/ | Dart isolate 层 + flutter_rust_bridge(FRB)绑定,内含 rust/(插件 crate rust_lib_localsend_app)与 rust_builder/(cargokit) |
| packages/core/ | Rust crate localsend:协议、HTTP 服务端/客户端、加密、WebRTC,无 Flutter 依赖 |
| packages/typed_isolates/ | 独立小包,用类型化的 send/receive channel 包装 Dart Isolate |
| server/ | Axum WebSocket 信令服务(WebRTC 的 /v1/ws),独立部署,见 server/Dockerfile |
| cli/ | Rust CLI crate localsend-cli,基于 packages/core 的交互式终端客户端(v2 HTTP + 组播) |
| support/scripts/ | 发布/打包脚本(各平台构建、MSIX、Inno Setup、FOSS 剥离) |
两个 workspace 的划分值得注意(均为 AGENTS.md 明确说明、且可在仓库中直接验证):
- Cargo workspace:四个 Rust crate(
packages/core、packages/localsend_isolates/rust、server、cli)构成以仓库根 Cargo.toml 为根的单一 workspace,共享一份Cargo.lock和target/;[profile.*]设置只写根Cargo.toml(成员里的 profile 会被忽略)。根Cargo.toml中可以看到debug = "line-tables-only"的 dev profile,以及针对rsa、num-bigint-dig的opt-level = 2特化(因为 RSA 大数运算未优化时慢约 10 倍,dev 下保持加密 crate 优化才能让测试足够快)。 - pub workspace:三个 Dart 包(
app、packages/localsend_isolates、packages/typed_isolates)构成以根 pubspec.yaml 为根的 pub workspace,共享一份pubspec.lock和.dart_tool/,在任意成员目录执行pub get即可解析;rust_builder与 vendored 的cargokit/build_tool被刻意排除在 workspace 之外,仍独立解析。
依赖方向被严格约束为:app → localsend_isolates → (typed_isolates、rust_lib_localsend_app → localsend core)。应用层只依赖 localsend_isolates,不直接依赖 flutter_rust_bridge、typed_isolates 或插件 crate。
三、标准开发命令(Dart / Flutter 侧)
除特别说明外,以下命令均在 app/ 下执行,且按 CLAUDE.md 的要求全部走 fvm:
fvm flutter pub get
fvm dart run build_runner build # dart_mappable, freezed, flutter_gen, mockito
fvm dart run slang # i18n 代码生成(slang_build_runner 在 build.yaml 中被禁用)
fvm flutter run
CI 实际执行的检查命令("Checks (what CI runs)"):
fvm dart format --set-exit-if-changed lib test # CI 会先删除 lib/gen;生成代码不做 format 检查
fvm flutter analyze
fvm flutter test
fvm flutter test test/unit/util/security_helper_test.dart # 跑单个文件
fvm flutter test --plain-name 'some test name' # 跑单个用例
这里有两个容易踩坑的细节,AGENTS.md 都专门点名了:
- 格式化宽度是 150 列:配置在 app/analysis_options.yaml 的
formatter段(page_width: 150、trailing_commas: preserve)。任何把生成 Dart 按 80 列重排的工具都会制造纯噪音,应事后用fvm dart format统一。 - FRB 代码生成的副作用:
flutter_rust_bridge_codegen generate(在packages/localsend_isolates/下运行,配置见 packages/localsend_isolates/flutter_rust_bridge.yaml,其中dart_format_line_length: 150)有时会按 80 列重写app/test/mocks.mocks.dart——如果 diff 里出现该文件,直接还原。
另外 packages/localsend_isolates 有自己的 build.yaml,当它的 model 变化时需要在该包内单独跑一次 build_runner(pub get 则通过 pub workspace 共享)。CI 还会额外在 packages/localsend_isolates/rust_builder/cargokit/build_tool 里跑一次 flutter pub get。
模型层的代码生成约定也写死在 app/build.yaml:dart_mappable_builder 通过 renameMethods 把 fromJson/toJson 改名为 deserialize/serialize(Map 版本变为 fromJson/toJson);slang_build_runner 被禁用(i18n 用独立的 fvm dart run slang 命令)。
四、Rust 侧命令与 core crate 的 feature 门禁
cargo test --features full # 在 packages/core 下 —— 见下文的 "Core crate features"
cargo clippy --features full
cargo check # 在 packages/localsend_isolates/rust、server、cli 下
AGENTS.md 对 packages/core 的 feature 门禁给出了强制要求:永远用 --features full 构建和测试。这一点在 packages/core/Cargo.toml 中可以直接验证:
[features]
default = []
crypto = ["ed25519-dalek", "rcgen", "rsa", "sha2", "tokio-util"]
discovery = ["http", "multicast"]
http = ["crypto", "form_urlencoded", "http-body-util", "hyper", "hyper-util", "if-addrs", "pem", "percent-encoding", "reqwest", "rustls", "socket2", "time", "tokio-rustls", "tokio-util", "x509-parser"]
multicast = ["if-addrs", "socket2", "tokio-util"]
webrtc-signaling = ["tokio-tungstenite"]
webrtc = ["crypto", "flate2", "dep:webrtc", "webrtc-signaling", "x509-parser"]
full = ["crypto", "discovery", "http", "multicast", "webrtc"]
即 core crate 把 crypto、http、multicast、webrtc、webrtc-signaling 几乎全部功能都藏在 feature 后面,default = []。AGENTS.md 特别警告:裸的 cargo check/cargo build 会失败——因为模块是无条件声明的,而它们的依赖是可选的;这是仓库既有的预期行为,不是回归。
五、架构要点:Agent 需要知道的运行时事实
AGENTS.md 的 "Architecture" 一节为 LLM 提供了大量「不读源码就会做错」的运行时约束,逐条对应仓库中的真实实现:
状态管理:Refena 而非 Riverpod
Provider 位于 app/lib/provider/;纯状态用 NotifierProvider,凡是 isolate 层会触碰的状态一律用 ReduxProvider + 分发的 action 类。启动引导在 app/lib/config/init.dart 的 preInit:初始化日志、RustLib.init()、持久化、isolate 容器、托盘/窗口,最后返回 main.dart 挂载用的 RefenaContainer。
Isolate 模型:重网络绝不跑在主 isolate
packages/localsend_isolates/lib/src/isolate/ 的分工是:
parent/parent_isolate_provider.dart——ParentIsolateState为每个子 isolate(http 扫描发现、组播发现、http 上传、http 服务)持有一个IsolateConnector,外加一份同步到所有子 isolate 的SyncState;parent/actions.dart、parent/actions_sync.dart——应用与子 isolate 通信的唯一支持方式;child/*_isolate.dart——子 isolate 入口,把类型化任务消息翻译成lib/src/task/的调用;lib/src/task/——只允许纯 helper,isolate 逻辑被明确禁止出现在那里(该目录有独立的 README 重申这一点)。
子 isolate 需要的状态(别名、端口、协议、服务器是否运行、web send 是否开启)通过 IsolateSyncServerStateAction 下推;子 isolate 启动时读取 syncState,因此必须先同步、再启动服务器。
Rust 网络层:事件通道而非请求响应
HTTP 服务端/客户端用 Rust 实现(packages/core/src/http/server/),只服务协议 v2(v1 端点不提供服务),另含 "web send" 下载流(server/web.rs)和一个用于把已在运行的实例拉回前台的内部 show 端点。
集成方式是 channel 式:start_with_port 接收 ServerConfigV2 { pin, event_tx, web_send },向外发出 ServerEventV2 事件(Register、带 decision_tx oneshot 的 PrepareUpload、带字节流和 result_tx 的 FileUpload、PrepareDownload、SessionEnd 等)。关键约束:
- 同一时刻只有一个上传会话活跃;取消安全性由 drop 守卫(
PendingSessionGuard、UploadGuard、PendingWebSessionGuard)保证; - core 中刻意没有
auto_accept——应用层通过立即应答decision_tx实现自动接受; - 新的 server→app 交互应当扩展
ServerEventV2,而不是新增旁路 channel。
FRB 层(packages/localsend_isolates/rust/src/api/server.rs)暴露 start_server 与不透明的 RsHttpServer,其 listen 把 v2、web-send、内部三条 channel 合并为单一 RsServerEvent 流;respond oneshot 留在 Rust 侧。Dart 侧 child/server_isolate.dart 将其转成 HttpServerEvent,再由 app/lib/provider/network/server/server_provider.dart 路由给 ReceiveController / SendController——它们是事件处理器,不是路由处理器。
安全模型上:TLS 使用每设备即时生成的证书,客户端证书强制(web 页面阶段可选,以便浏览器接入);对端身份是客户端证书 DER 的大写十六进制 SHA-256,若 payload 声称的指纹与证书不符,Register 事件直接不发出。事件中的 ip 是 PeerIp(IP + IPv6 scope),链路本地对端会呈现为 fe80::1%3 这种可继续回拨的地址。接收 pin 与 web-send pin 都在服务器启动时固定,改动任一值都会导致服务器重启。
组播发现
packages/core/src/multicast/(feature multicast,独立于 http)实现协议 v2.2 的 UDP 组播发现(不解析 v1 消息):multicast::start 接收 MulticastConfig { group, group_v6, port, interface_filter, device, event_tx },发出 MulticastEvent::Discovered { ip, message };返回的 MulticastHandle 提供 announce(通告突发)与 wait_stopped。UDP 只做通告,回复走 HTTP 单播 register 请求。实现上每个接口 IPv4 地址绑定一个 socket(SO_REUSEPORT/SO_REUSEADDR + IP_MULTICAST_IF),组播 loopback 保持打开让同主机多实例互见、用指纹丢弃自己的消息;IPv6 组播组 ff12::fd3a:e420(DEFAULT_MULTICAST_GROUP_V6)是 LocalSend 私有扩展,Discovered 事件携带源 scope ID,链路本地 IPv6 源的 HTTP 回复需要它。
i18n 与 FOSS 构建
Slang 负责 i18n:源文件在 app/assets/i18n/(<locale>.json 加 _missing_translations_<locale>.json),生成物在 app/lib/gen/;以 @ 开头的字段是翻译者的元数据,应用不消费。app/test/unit/i18n_test.dart 守护 locale 集合的一致性(对应 CONTRIBUTING.md 的 "Translation" 流程)。
F-Droid 的 FOSS 构建由 support/scripts/remove_proprietary_dependencies.sh 剥离 in_app_purchase 与捐赠 UI,机制是依赖 # [FOSS_REMOVE] pubspec 标记和 // [FOSS_REMOVE_START] / // [FOSS_REMOVE_END] 注释对——编辑 lib/config/init.dart、lib/pages/donation/*、lib/provider/purchase_provider.dart 时必须保留这些标记。
六、发布一致性:版本号三处同步
AGENTS.md 的 "Release notes" 要求 app/pubspec.yaml 的版本与 support/scripts/compile_windows_exe-inno.iss 的 #define MyAppVersion、cli/Cargo.toml 的 version(CLI 启动 banner 会打印它)三者一致,否则 CI 失败。当前仓库三处均为 1.18.2(app 为 1.18.2+64),可以据此验证一致性约束真实存在。各平台的构建命令与发布流程见 README.md 的 "Building" 一节与 CONTRIBUTING.md 的 "Release" 一节。
七、AI 贡献政策与 Agent 指南的编写范式
AGENTS.md 开头同时声明了 LocalSend 的 AI 贡献政策:不接受 AI 生成的贡献,除非它们是 bug fix、规模极小、或贡献者能证明自己在该领域的专业水平。这也解释了 CLAUDE.md 的存在定位——它不是给人类开发者的 onboarding,而是给「被允许进入仓库的 LLM」的防错护栏。
从工程实践角度看,CLAUDE.md + AGENTS.md 的组合提供了一个值得参考的「Agent 仓库指南」范式:
- 入口极简:CLAUDE.md 只写一条最高优先级规则(fvm)+ 一个
@AGENTS.md引用,把细节全部外置; - 规则全部可验证:fvm 要求对应
.fvmrc,150 列格式对应analysis_options.yaml,feature 门禁对应packages/core/Cargo.toml,版本一致性对应 CI 与三个版本文件——每条约定都能在仓库中找到落点,而非空泛口号; - 显式列出反直觉点:比如「裸
cargo check失败是预期行为」「codegen 会污染mocks.mocks.dart」「先同步再启动服务器」,这些恰是 Agent 最容易误判为回归或自行"修复"的地方。
小结:在 LocalSend 仓库中工作(无论人还是 Agent),操作路径是固定的——用 fvm flutter / fvm dart 走 Dart 侧(pub get → build_runner → slang → run/test),用 cargo test --features full 走 Rust core,改动 isolate 相关 model 时记得 packages/localsend_isolates 需要单独的 build_runner 运行,改捐赠/购买相关代码时保留 [FOSS_REMOVE] 标记,发版前核对三处版本号。CLAUDE.md 虽然只有八行,但它加 AGENTS.md 覆盖的正是这条完整链路。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00