Skip to content

fix(cli): reload config edits, add dispatch controller, dedupe bot device list - #3648

Merged
bobleer merged 5 commits into
GCWing:mainfrom
bobleer:bob/cli-config-reload-dispatch-devices
Oct 31, 2026
Merged

bobleer merged 5 commits into
GCWing:mainfrom
bobleer:bob/cli-config-reload-dispatch-devices

Conversation

@bobleer

@bobleer bobleer commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Three CLI host problems reported from a WeChat-driven daemon:

  1. Config edits needed a restart. No host watched app.json; the CLI daemon kept serving the config it loaded at startup (e.g. ListModels stayed stale after editing the model list). Core now polls the config file every 2 s and reloads when another writer changed it.
  2. No CLI dispatch controller. openbitfun dispatch <verb> is the target-side protocol; there was no way to submit a job from the CLI. Adds openbitfun dispatch outbound targets|submit|status|list|cancel.
  3. Bot device menu listed the host twice. The IM bot menu added a synthetic "本地设备" entry plus every online account device, including the host serving the bot.

Type and Areas

Type: bug fix / feature

Areas: Rust core (config, dispatch, remote-connect bot), CLI, desktop/Tauri (dispatch adapter refactor), core boundary script, docs

Motivation / Impact

  • Config reload (service/config): ConfigManager records a fingerprint of the content it last loaded or wrote; ConfigService::reload_if_changed_on_disk reloads and broadcasts ModelConfigurationUpdated + ConfigReloaded only when another writer changed the file. Own writes do not trigger reloads; invalid mid-edit content keeps the current config (warned once per distinct error); a deleted file is not recreated; reloads do not fire local_changes, so account sync does not echo them. The poller is started by GlobalConfigManager::initialize under agent-runtime, so CLI daemon, desktop, and server all benefit. Polling rather than a file watch keeps atomic replacement and network home directories working.
  • CLI dispatch controller: thin JSON surface over the existing Core controller (submit_device_dispatch, submit_dispatch, etc.). --target accepts a device name/alias, device:<id>, or ssh:<connectionId>; the local device is excluded from targets; offline devices fail without local fallback; --approval-policy is required (auto or reject-and-report; remote is not offered because the CLI has no answer command yet). The account-device host_invoke envelope and agent-identity translation moved from the desktop adapter into Core (AccountRuntimeDeviceDispatchRpc, encode/decode_device_dispatch_*) so desktop and CLI share one implementation. dispatch-store is admitted into the reviewed CLI Core closure; the boundary script keeps it forbidden for every other reviewed closure.
  • Device menu: the relay entry whose id matches DeviceIdentity::from_current_machine() is folded into the local entry (本地设备 (<host name>)); a chat that had selected its own host as a remote device is returned to the local target. Desktop and mobile web pickers already filtered the self device.

Remote scenarios: remote control (IM bot device menu) and Detached Dispatch controller paths are covered by unit tests only. No live relay, WeChat, SSH, or second-device run. The dispatch wire protocol (v6) and the target verbs are unchanged.

Upgrade compatibility: no persisted shape changes. The config fingerprint is in-memory only.

Verification

  • cargo test -p openbitfun-core --lib --features agent-runtime,ssh-remote,remote-connect,dispatch-store,web-tools -- device_menu_ service::config device_dispatch_envelope (96 passed), including new reload_if_changed_on_disk_picks_up_external_edits_only, device_menu_folds_this_host_into_the_local_entry, device_dispatch_envelope_round_trips_success_and_errors
  • cargo test -p openbitfun-cli --bin openbitfun -- dispatch_command_tests dispatch::outbound (3 passed)
  • cargo test -p openbitfun-desktop --lib dispatch_api (2 passed)
  • cargo check -p openbitfun-cli, cargo check -p openbitfun-desktop --tests
  • pnpm run check:core-boundaries, pnpm run check:repo-hygiene
  • Real process, isolated OPENBITFUN_USER_ROOT/OPENBITFUN_HOME: started openbitfun --verbose server, appended a model to app.json; the log showed exactly one Configuration reloaded after an on-disk change about 2 s after the edit and no reloads before it.
  • Real process, signed out: dispatch outbound targets and list print []; submit to a device and status for an unknown job print a JSON error and exit 1 without local fallback.

Related-surface review: Desktop DeviceStatusControl, the peer-device roster, mobile web DevicesPage/SessionListPage, and the CLI TUI account panel already filter or mark the self device, so the IM bot menu was the only duplicate. Restored bot state already drops remote device selections, so a stale self-selection cannot survive a restart. The poller resolves the global config service on each tick so a replaced service is never left stale.

Checked refreshed upstream main and open PRs before implementation; none covered these issues.

Reviewer Notes

  • The dispatch-store ban for the CLI came from the build-speed closure isolation (0e64e80), not a semantic rule; its extra dependencies (base64, services-core/dispatch-workspace) were already in the CLI closure.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable.

The IM bot device menu added a synthetic local entry and then every online
account device, including the host serving the bot, so that host appeared
twice. Fold the relay entry whose id matches the local device identity into
the local entry, and return a chat that had selected its own host as a
remote device to the local target.
Long-running hosts (CLI daemon, desktop, server) kept the configuration loaded
at startup until an in-process reload, so hand edits or another process's
writes needed a restart. Track a fingerprint of the content the config manager
last loaded or wrote and poll the file every 2 seconds; reload and broadcast
only when another writer changed it. Invalid mid-edit content keeps the
current configuration, a missing file is never recreated, and reloads do not
signal local edits to account sync.
The CLI only implemented the target side of Detached Dispatch. Add
`openbitfun dispatch outbound targets|submit|status|list|cancel`, a JSON
controller over the Core dispatch owner for saved SSH connections and
same-account devices, excluding this host.

Move the account-device host_invoke envelope and agent-identity translation
into Core (AccountRuntimeDeviceDispatchRpc) so Desktop and CLI share one
implementation, and admit dispatch-store into the reviewed CLI Core closure.
Resolve the global config service on every poll instead of capturing the
instance created at initialization, so a replaced service is not left stale.
Update the CLI reviewed Core feature fixture and replace the blanket
dispatch-store exclusion test with one that allows the CLI controller and
still rejects the feature for another reviewed closure.
@bobleer
bobleer merged commit 7eda0f6 into GCWing:main Oct 31, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant