Skip to content

[codex] Add automation radio connect command#3851

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/automation-connect-command
Jun 27, 2026
Merged

[codex] Add automation radio connect command#3851
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/automation-connect-command

Conversation

@rfoust

@rfoust rfoust commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds semantic automation-bridge support for showing the Connect to Radio dialog and for connecting/disconnecting through the real Connect to Radio workflow. The bridge now supports connect list, connect show, connect hide, connect local first, connect local serial <serial>, connect ip <host-or-ip>, connect wait <timeout_ms>, and disconnect, all routed through ConnectionPanel / MainWindow / RadioModel instead of bypassing application logic. Also adds stable object/accessibility names to connection-panel controls and documents the new commands in the automation bridge guide.

Constitution principle honored

Principle VII — boundary input validation. The new automation commands validate selectors, timeouts, and connection state at the bridge boundary, then route only valid requests through the existing connection UI/model path.

Test plan

  • Local build passes (cmake --build build --target AetherSDR --parallel)
  • Behavior verified on a real radio if applicable
    • Visible app run connected to a local FLEX-6700 through connect local serial <serial>, verified connected:true, then disconnected and verified connected:false. Local serial/IP details intentionally omitted from the public PR body.
  • Existing tests pass (CI)
  • Reproduction steps documented if user-reported bug
    • Verified both offscreen and visible automation flows with bridge smoke checks.

Additional local validation:

  • cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
  • cmake --build build --parallel
  • cmake --build build --target AetherSDR --parallel
  • python3 -m py_compile tools/automation_probe.py
  • python3 tools/check_a11y.py src/gui/ConnectionPanel.cpp src/gui/MainWindow.cpp
  • git diff --check
  • Offscreen bridge smoke:
    • tools/automation_probe.py ping
    • tools/automation_probe.py connect list
    • tools/automation_probe.py connect show
    • tools/automation_probe.py connect hide
    • tools/automation_probe.py connect wait 5
  • Visible bridge smoke:
    • tools/automation_probe.py connect local serial <serial>
    • tools/automation_probe.py connect wait 30000
    • tools/automation_probe.py get radio
    • tools/automation_probe.py disconnect

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls — use nested-JSON-under-one-key
    (Principle V)
  • Code is clean-room — not decompiled, disassembled, or
    reverse-engineered from a proprietary binary (Principle IV)
  • All meter UI uses MeterSmoother (AGENTS.md convention)
    • Not applicable: no meter UI changed.
  • Documentation updated if user-visible behavior changed
  • Security-sensitive changes reference a GHSA if applicable
    • Not applicable.

@rfoust rfoust marked this pull request as ready for review June 27, 2026 01:04
@rfoust rfoust requested review from a team as code owners June 27, 2026 01:04
Copilot AI review requested due to automatic review settings June 27, 2026 01:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds automation-bridge support for connecting/disconnecting via the real Connect to Radio workflow (ConnectionPanel → MainWindow → RadioModel), plus documentation and probe-script updates to exercise the new verbs.

Changes:

  • Adds connect/disconnect verbs to the in-app AutomationServer, including connect wait with a deferred response.
  • Exposes a small automation facade on ConnectionPanel and assigns stable object/accessibility names to key connection controls.
  • Updates the automation probe script and bridge documentation to cover the new commands.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/automation_probe.py Adds CLI support/examples for connect and disconnect requests to the bridge.
src/main.cpp Wires AutomationServer to ConnectionPanel by locating the panel instance on MainWindow.
src/gui/ConnectionPanel.h Adds an automation-facing API surface for local/IP connect + disconnect triggers.
src/gui/ConnectionPanel.cpp Implements automation connection helpers, adds stable object/accessibility names, and improves hostname probe address handling.
src/core/AutomationServer.h Documents new verbs and adds ConnectionPanel hook + deferred connect-wait tracking state.
src/core/AutomationServer.cpp Implements connect/disconnect verbs, deferred connect wait, and shared JSON response writing.
docs/automation-bridge.md Documents connect/disconnect usage in both JSON and bare-line forms.

Comment on lines +2060 to +2080
if (selector.isEmpty() || selectorLower == QLatin1String("first")) {
QPointer<ConnectionPanel> guardedPanel = panel;
QTimer::singleShot(0, qApp, [guardedPanel] {
if (!guardedPanel) {
return;
}
QString error;
if (!guardedPanel->automationConnectLocalFirst(&error)) {
qCWarning(lcAutomation).noquote()
<< "connect local first failed after scheduling:" << error;
}
});
return QJsonObject{
{QStringLiteral("ok"), true},
{QStringLiteral("connect"), QStringLiteral("local")},
{QStringLiteral("selector"), QStringLiteral("first")},
{QStringLiteral("requested"), true},
{QStringLiteral("deferred"), true},
{QStringLiteral("radio"), connectionRadioToJson(radios.first())},
};
}
@rfoust rfoust self-assigned this Jun 27, 2026
@rfoust rfoust force-pushed the codex/automation-connect-command branch from 04d469e to b44f5bc Compare June 27, 2026 01:27
@rfoust rfoust requested a review from a team as a code owner June 27, 2026 01:27
@rfoust

rfoust commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up pushed in b44f5bcf:

  • Added idempotent connect show / connect hide automation verbs for the Connect to Radio dialog. These route through MainWindow::showConnectionDialog() / hideConnectionDialog() instead of using the existing menu toggle, so connect show is safe when the dialog is already open.
  • Addressed the Copilot review note on connect local first: the command now captures the first discovered radio serial before deferring and then connects by serial, so discovery updates between response and execution cannot change the target.
  • Updated docs/automation-bridge.md and tools/automation_probe.py for the new dialog verbs.

Validation after the follow-up:

  • cmake --build build --target AetherSDR --parallel
  • python3 -m py_compile tools/automation_probe.py
  • python3 tools/check_a11y.py src/gui/ConnectionPanel.cpp src/gui/MainWindow.cpp
  • git diff --check
  • Bridge smoke: ping, repeated connect show, connect hide, then connect show after hidden.

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, @rfoust — this is a well-structured addition. Routing every connect/disconnect through ConnectionPanelMainWindowRadioModel instead of bypassing app logic is the right call, and the boundary validation (empty serial, unknown selector, already-/not-connected guards, clamped wait timeout) is thorough. The ConnectWait lifecycle is handled correctly in all three teardown paths (stop(), onDisconnected(), finishConnectWait() — timer stopped + deleteLater, signal disconnected, erased from the vector), and deferring the actual connect onto a singleShot(0, qApp, …) to avoid running modal connection-conflict UI inside the local-socket read callback is a nice touch. I verified the RadioInfo fields used in connectionRadioToJson all exist, and CI is green across all six checks.

A few notes:

Copilot's finding is a false positive. Copilot flagged that connect local first schedules automationConnectLocalFirst(), which re-selects row 0 at execution time and could target a different radio if discovery reorders m_radios. But that's not what the code does — doConnect captures selected.serial synchronously before scheduling, then defers automationConnectLocalSerial(selectedSerial), which re-resolves by serial. That's exactly the deterministic behavior Copilot recommended, and the response's serial/radio fields stay consistent with what actually gets targeted. No change needed here.

Minor — dead code. A consequence of the above: ConnectionPanel::automationConnectLocalFirst() (ConnectionPanel.cpp:766 / .h:34) is now never called — the bridge uses the capture-serial-then-automationConnectLocalSerial path instead. Consider removing it, or keep it only if you intend it as a documented convenience API.

Nice correctness fix in scope. The probeRadio change to fall back to sock->peerAddress() when QHostAddress(trimmedIp) is null correctly handles the connect ip <hostname> case the docs advertise, where the old QHostAddress(trimmedIp) would have produced a null address for a hostname.

Heads-up (not blocking): the PR currently shows as merge-conflicted against main (base is a few commits behind) — it'll need a rebase before it can merge.

Overall this looks solid and ready once the dead-method nit and rebase are sorted.


🤖 aethersdr-agent · cost: $4.4341 · model: claude-opus-4-8

@rfoust rfoust force-pushed the codex/automation-connect-command branch from b44f5bc to b94f3f4 Compare June 27, 2026 02:27
@rfoust rfoust force-pushed the codex/automation-connect-command branch from b94f3f4 to dccfd18 Compare June 27, 2026 02:30
@rfoust

rfoust commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Update after the latest review pass:

  • Removed the now-dead ConnectionPanel::automationConnectLocalFirst() helper called out by aethersdr-agent. connect local first continues to capture the selected serial synchronously and defer through automationConnectLocalSerial(...).
  • Rebasing is complete on current upstream/main (59a19681). The conflicts were resolved by preserving upstream automation docs/startup verbs (close, drag, showMenu, expanded pan) and keeping the connect/disconnect additions alongside them.
  • Rechecked PR review threads after the push. The Copilot thread is now outdated, and GitHub reports the PR as blocked only for required review, not merge conflicts.

Validation on final head dccfd180:

  • cmake --build build --target AetherSDR --parallel
  • python3 -m py_compile tools/automation_probe.py
  • python3 tools/check_a11y.py src/gui/ConnectionPanel.cpp src/gui/MainWindow.cpp
  • git diff --check
  • Conflict/stale-helper search: no automationConnectLocalFirst references and no conflict markers in touched files.
  • Bridge smoke against the final build: ping, get radio with connected:true / transmitting:false, connect show from hidden, repeated connect show idempotence, connect hide, disconnect, then get radio with connected:false / transmitting:false.

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Reviewed the security posture + correctness:

  • Opt-in only — the bridge (and these verbs) start only with AETHER_AUTOMATION set, over a local socket; no new always-on/remote surface.
  • No bypass — connect/disconnect route through ConnectionPanel → MainWindow → RadioModel, respecting multiflex/conflict UI.
  • TX-safety intact — connecting establishes a session, it doesn't key; kTxKeyingProperty/ALLOW_TX still gates every keying verb.
  • Boundary validation (Principle VII) thorough (empty serial / unknown selector / already-/not-connected / clamped wait).
  • Lifecycle clean: ConnectWait torn down in all three paths; connect deferred via singleShot(0) so modal conflict UI never runs in the socket callback.
  • Copilot's connect local first race is a false positive — serial is captured synchronously then re-resolved by serial.

One non-blocking nit (dead automationConnectLocalFirst()) filed as a fast-follow. CI green on all 6. Thanks @rfoust — clean, and exactly the verb I was missing for bridge-driven debugging.

@ten9876 ten9876 merged commit 25ee21e into aethersdr:main Jun 27, 2026
6 checks passed
ten9876 added a commit that referenced this pull request Jun 28, 2026
## Release prep for v26.6.5

Docs + version-string updates for the **50 merges** since v26.6.4. No
code change — safe to merge without a rebuild.

### Changes
- **CHANGELOG.md** — new `[v26.6.5] — 2026-06-28` section, headlined by
**KiwiSDR receive sync** (#3872), **SmartMTR TX meters** (#3776), the
**PROF profile-switcher applet** (#3829), and a large **agent automation
bridge** expansion (#3851/#3856/#3883/#3842/#3832/#3819); categorized
Added/Changed/Fixed/Performance/Removed compiled from the full merge
list. Folds in the prior `[Unreleased]` entries and resets
`[Unreleased]` to empty.
- **CMakeLists.txt / README.md / AGENTS.md** — `26.6.4` → `26.6.5`.
- **ROADMAP.md** — current cycle → `post-v26.6.5`; four v26.6.5
highlights added atop *Recently shipped*.

### Not included
- Tagging `v26.6.5` is a separate, explicit step (not done here).
- The untracked `docs/aetherd-headless-engine-design.md` RFC and
`prototypes/` are intentionally left out.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants