* feat(agents): mobile_ui agent tool for Android UI control (PR 3/3)
Add a dedicated model-facing tool that drives another Android app through the
PR2 mobile.ui.observe/act node commands, completing the vertical slice
(agent -> tool -> node.invoke -> AccessibilityService).
- Mirrors the desktop computer tool's safety model: owner-only + HTTP-denied
(dangerous-tools), raw node.invoke of mobile.ui.* redirected to this tool so
the generic nodes tool cannot bypass it, run/tool-call idempotency, and the
phone-arm workflow (mobile.ui.* must be explicitly armed).
- One call is observe or one act; every act automatically re-observes for
postcondition verification and preserves the landed outcome if re-observe fails.
- Fail-closed confirmation: all state-changing acts (activate, set_text, tap,
swipe) require confirmed=true after the model reviews the proposed effect;
observe, scroll, wait, and navigation global_actions do not. The keyword list
only enriches the confirmation message and is never the sole gate (a11y labels
are localized/iconographic/coordinate-blind).
- Node selection resolves an explicit id against the full device set first
(case-insensitively) and rejects an ineligible or ambiguous match, so an
explicit selection can never be silently redirected to the wrong phone.
- All observed UI text is treated as untrusted; the tool description forbids
following instructions found in app UI.
Additive; no protocol bump. mobile-ui-tool + registration + policy tests pass;
core tsgo and lint verified on Testbox. On-device drive is exercised via the
PR1/PR2 emulator proof; this PR is the agent-side tool + tests.
Follow-up: computer-tool.ts has the same latent node-resolution ordering bug
(explicit id searched only among eligible nodes); tracked separately.
* fix(agents,phone-control): migrate mobile_ui arming hints/tests to gateway.nodes.commands.allow
Match main's node-command arming rename (gateway.nodes.allowCommands ->
gateway.nodes.commands.allow / commands.deny): update the mobile_ui tool arm-hint
matchers to the gateway's current rejection strings and fix the phone-control /
tool tests to the current config shape. Production write path was already correct.
* fix(agents): register mobile_ui in owner-only denylist; trim phone-control under max-lines
- tool-resolution.exclude.test.ts: mobile_ui joins the owner-only core
tools, so add it to the expected plugin/inherited denylists.
- phone-control: derive ArmGroup from a single ARM_GROUPS const and
collapse parseGroup's ||-chain, dropping index.ts back under 700 lines.
* fix(agents): centralize mobile_ui global-action names; regenerate tool-display snapshot
- Extract GLOBAL_ACTION_NAMES const so the schema, action type, and
validator share one source. This also removes the bare `name: "back"`
type-annotation literal that scripts/tool-display.ts's name-regex was
misreading as a phantom runtime tool.
- Regenerate apps/.../tool-display.json to add the mobile_ui display entry
(was missing from the Swift snapshot).
* refactor(extensions): remove dead QA and utility exports
* refactor(extensions): trim Matrix QA internal exports
* chore(deadcode): refresh extension export baseline
* chore(ci): reconcile deadcode and LOC baselines
* chore(deadcode): refresh baseline after main advance
* chore(deadcode): refresh baseline after main advance
* refactor(plugins): keep channel snapshot type private
* fix(ci): align Linux deadcode baseline
* fix(extensions): make indexed access explicit across channel plugins
Transport-payload-safe burn-down: malformed Telegram/Discord/QQ/LINE
and sibling channel input keeps existing skip paths; no synthesized
fields, no new throws in delivery loops. Zalo escape sentinels preserve
literal matches instead of undefined replacements.
* fix(extensions): make indexed access explicit across provider and memory plugins
Stream and model iteration, tool-block guards, capture guards, and
sparse accumulators; singleton model reads carry named invariants.
* fix(extensions): make indexed access explicit across tooling plugins, flip the extensions lane
Remaining plugins (oc-path, qa-lab, browser, logbook, and siblings) plus
the tsconfig.extensions.json flag flip. Cleanup: logbook sampleFrames
NaN index at max=1, QA retry clamp at non-positive attempts, dead Canvas
probe and OpenShell no-op slice removed, twitch test setup leak excluded
from the prod lane.
* refactor(plugin-sdk): expose expectDefined via a focused SDK subpath
Extensions imported @openclaw/normalization-core directly, crossing the
external-plugin packaging boundary (it only worked because the runtime
builder bundles undeclared workspace helpers). expect-runtime joins the
canonical entrypoints JSON, generated exports, API baseline, docs, and
subpath contract test; all 78 extension imports now use the SDK seam.
Two scanner-shaped locals renamed for review-bundle hygiene.
* chore(plugin-sdk): raise surface budgets for the expect-runtime subpath
One new entrypoint with one callable export, added intentionally as the
packaging-honest seam for extension invariant helpers.
* chore(release): close out 2026.6.10 on main
* chore(release): align native app metadata for 2026.6.10
* chore(release): sync Android 2026.6.10 notes
* docs(changelog): preserve 2026.6.9 history
* docs(changelog): preserve 2026.6.9 history
Add exec approvals artifact evidence to Policy.
- add the execApprovals policy namespace and check IDs for required artifact presence, default/per-agent security posture, autoAllowSkills, and allowlist drift
- read the active exec-approvals.json artifact only when execApprovals policy rules are configured, honoring OPENCLAW_STATE_DIR before the default ~/.openclaw path
- emit redacted posture evidence and stable oc:// references without socket tokens, command text, resolved paths, timestamps, or approval-session details
- document the public policy surface and add focused scanner, doctor, conformance, and CLI coverage
Validation:
- GitHub Actions for head b82eefe492 are green, including Real behavior proof.
- ClawSweeper re-review completed for the same head with proof: sufficient and status: ready for maintainer look.
- Maintainer artifact-boundary acceptance is recorded in the PR discussion and body.
Co-authored-by: Gio Della-Libera <235387111+giodl73-repo@users.noreply.github.com>