Repository navigation
Releases: il90il90/touchless-releases
Release list
Touchless v0.2.325
Touchless v0.2.325 (code 335)
Issue: #240 (owner, 2026-10-21): every visible feature must work. Nothing should be marked "not available yet".
Learn from screen recordings now works
- Settings ▸ Learned skills ▸ Learn from screen recordings (user-only opt-in, off by default). When it's on,
Learn from a recording opens a picker. You choose a screen recording and Touchless analyzes it on the
phone: it detects screen changes and runs ML Kit text recognition on each new screen. - Only a redacted text outline is saved. Runs of 4+ digits and secret-looking tokens are masked, and the video
is never uploaded, copied or kept. Outlines can be viewed and deleted. start_teaching source=recording recording_demo=<id|name>starts Teach Mode and returns the outline. The AI
performs the flow once and saves it as an ordinary replayable macro, so every confirmation and guard still
applies. With the opt-in off the answer isDISABLEDand nothing is read. With no analyzed recording the
answer isNEEDS_RECORDING_DEMO, along with the list of available recordings.- Limit: the on-device OCR reads Latin script, so Hebrew-only screens produce little text.
Other unfinished features made real
run_ui_test→compare_screenshotdoes a real pixel diff (max_diff_ratio,tolerance) and FAILs when
the screen differs. Before, it said "not implemented".- Cloud voice has a Settings card (endpoint, key, voice, language, plus a Test button that reports whether
cloud audio actually played). Before, it could only be configured over MCP. - Safety & permissions center is reachable from Settings ▸ Security & lock. It existed, but nothing opened it.
- Notification triggers: older rules still fire, so Settings now shows an entry to review, pause or delete
them. The entry appears only when such rules exist.
Removed from the UI (owner request)
- The Fast actions toggle and the Open Quick command button are hidden from Settings. The stored setting
is unchanged.
Verification: Android CI run 39683079624 passed (unit tests, debug + androidTest build, relay typecheck). Local
pure-Kotlin tests passed: PixelDiff 4, RecordingAnalysis 6, TeachSource 5, HardcodingGuard. Device E2E: NOT TESTED.
Touchless v0.2.324
Touchless v0.2.324 (code 334)
Issues: #117 (P0, device-verified root cause) and #217 (owner UX feedback on the AI Providers screen).
#117 — WhatsApp watches never fired on the S23: the contact has several numbers
Owner-authorized two-phone run on v0.2.321 (S23 ↔ Fold): a message from the Fold reached the S23 as a
WhatsApp notification whose sender resolved to an opaque Person key, and all three phone-bound watches
missed with sender_mismatch. The Fold's contact on the S23 has four phone numbers, so the #70
"exactly one number" rule in SenderResolver never resolved it.
SenderCandidates(pure, unit-tested): one distinct number resolves as before; several numbers that all
belong to one contact resolve to the number an ACTIVE watch is bound to; numbers spread over different
contacts with the same name, or no bound number, stay unresolved ("never match the wrong person on a name").- Contact queries carry
CONTACT_ID;EventHub.boundActorRefs(Room flow of the active watches' actorRefs)
feeds the resolver on the notification thread. - The reverse direction (S23 → Fold) passed on v0.2.321 for
log,reply_exact(reply visible on the other
phone),notifyandqueue_for_ai; the Fold → S23 re-run on this build is the acceptance test.
#217 — AI provider setup: one key, nothing else
- The AI Providers screen paints the theme background (it rendered on a white window with an invisible title)
and is now vendor + API key + Save. Name, endpoint, model (+ Discover), pricing, pull-only mode and the
plain-http LAN opt-in moved under Advanced; each vendor has a sensible default model (OpenAIgpt-4o-mini,
Anthropicclaude-3-5-haiku-latest, Geminigemini-2.0-flash, xAIgrok-3-mini); a local/custom server
takes the first model it lists on Save. - Home gets an "AI provider" card under the connection card (which provider answers, ready / not ready).
Opening it — from Home or Settings — asks for the phone's fingerprint / lock first, the same gate as the
private MCP link, because the screen holds API keys.
Testing
- Local pure-Kotlin harness: Event Hub package green (incl.
SenderCandidatesTest). - Android CI: one dispatch at completion (unit tests + debug/androidTest build + relay typecheck).
- Device: Fold → S23 WhatsApp re-run after install (reported on #117).
Touchless v0.2.323
Touchless v0.2.323 (code 333)
Release issue: #241. Builds on v0.2.322.
Copy MCP URL is now at the top
- The Copy MCP URL button moved from the "Connect your AI" card to a small pill in the Home header,
next to the licence pill. - Copying asks for your fingerprint, face or phone PIN first (Android's own prompt; change it in
Settings → Security & lock). The ⋮ menu next to it holds Show as QR and Revoke & regenerate,
and both ask for the same confirmation. Revoke didn't ask before. - The Connect card is gone from Home. Connection status is still shown on the agent card.
"?" — learn Touchless with your AI
- The ? button no longer opens the static "What Touchless can do" list. It shows the AI apps installed
on your phone (ChatGPT, Claude, Gemini, Grok, Perplexity, Copilot, DeepSeek, Mistral — only the ones you
have), plus Other app… and Copy prompt only. - Pick one and it opens with a ready prompt. Press Send and you get a complete guide to Touchless in your
phone's language: every tool, every permission (what it does, what stops working without it, how to grant
it), every screen, setup steps, automations, privacy, troubleshooting and examples, with icons and tables. - The prompt is built from the app itself, so it always matches your version. It never includes your
private connection link or token. It is also copied to the clipboard in case an app doesn't fill it in.
Testing
- Android CI (one dispatch): unit tests + debug/androidTest build + relay typecheck.
- New
HelpPromptTest(prompt contents, trimming, never the link/token). - Test infrastructure: the diagnostics journal no longer opens its database under Robolectric. That async
open raced test teardown and crashed unrelated tests. - Device check: NOT TESTED yet (owner checks the header pill and the "?" chooser).
Touchless v0.2.322
Touchless v0.2.322 (code 332)
Release issue: #238. Builds on v0.2.321.
#238 — Install APKs straight to the phone (upload → install → auto-delete)
Your AI can now send an APK to the phone without hosting it at a link.
create_apk_upload(served by the relay) returns a single-use upload link that is valid for one hour.
The AI uploads the file to it (curl -fT app.apk <upload_url>), or you open the link in any browser and
pick the APK. The connection token is never put in a URL; the link's random id is the one-time credential.install_uploaded_apk: the phone downloads the file from the relay with its own token, verifies it
(optionalsha256/package_name), deletes the relay copy, and opens the system installer. You
still confirm the install.- Nothing stays on the server. If the phone's delete doesn't arrive, the relay removes the copy as soon
as the phone reports the download. Anything not installed is wiped automatically after an hour. - Limits: ZIP/APK files only, up to 95 MB, at most 3 open uploads per connection.
- Works the same on the default Cloudflare relay and on a self-hosted relay (set
RELAY_PUBLIC_URLso
upload links use your public https address). Seedocs/SELF_HOSTING.md.
Testing
- Self-hosted relay:
npm run test:selfhost, including the newuploads.test.mjs(upload page, auth,
non-APK rejection, single use, token isolation, download and relay delete, slot cap). - Worker run locally with
wrangler dev: a 60 MB upload and download matched byte for byte, and a simulated
phone completed the install, after which the relay copy was deleted. - Android unit tests: tool parity, safety, onboarding and the new
UploadedApkTest. - NOT TESTED: a real APK on a physical device through the production Cloudflare relay.
Touchless v0.2.321
Touchless v0.2.321 (code 331)
Release issue: #217 (P0). This is a bundle release closing out the actionable board in one signed
build (owner policy: one regular release per completed delivery, never per commit). Issues covered:
#217, #149/#150 (code half), #104, #133, #110, #105, #27, #145, #35, #50, #151, #29, #174, #111, #114,
#23, #12. Draft PRs #180 and #186 were re-implemented on current main and closed as superseded.
#217 — autonomous AI replies (real provider inference, same conversation, exactly once)
- One engine, not two. v0.2.320 (released by the parallel session an hour earlier) shipped the same feature as
generate_with_aiwith its own runner. This release keeps that action name as an alias (existing watches and
their camelCase payloads keep working) and routes both names through the singleAutonomousReplyRuntime,
which carries everything v0.2.320's runner lacked: theThreadReplyGuard/DraftReplyStoreexactly-once send,
same-conversation transcripts, echo verification and the per-row lifecycle. v0.2.320's pure planner
(AutonomousReplyPlanner: pull-only → QUEUED, missing key → WAITING_PROVIDER), usage counters
(ProviderUsageStore) and the Settings → AI Providers screen are kept and wired into that runtime. - Keys and endpoints are Settings-only.
configure_ai_providerover MCP rejectsapi_key,base_urland
trust_lan_http: a remote AI can rename a connection, pick its model or make it the default, but can never
store a key or redirect where messages are sent. - Root cause fixed: only
queue_for_aiwatches hand an event to the Agent Router;log/notify/
reply_exact/run_scriptno longer leakagent_eventrows to the AI (owner's live finding #1). - New Event Hub action
ai_reply: Touchless itself calls a configured provider API (OpenAI-compatible,
Anthropic Messages, Gemini generateContent) with the incoming message quoted as untrusted data plus the
owner'sinstructions, resumes the same per-contact conversation (durable transcript keyed
connection | package | verified thread; restart-safe, bounded), sends exactly once through the shared
ThreadReplyGuard/DraftReplyStore(unverified sender / group / self / rate limit → held as a draft,
never sent), and verifies the outbound send when the app echoes the owner's own message. - Honest lifecycle on the delivery row, with correlation ids:
PROVIDER_WOKEN → CONVERSATION_RESUMED → AI_REPLIED → UNKNOWN (attempted) → OUTBOUND_VERIFIED, orDRAFTED/AI_DECLINED/FAILED.
list_event_deliveriesreturnscorrelationId, action-awareoutcomeandconfirmedSend. - Provider connections: Settings → AI Providers (keys encrypted on-device, never returned) plus the MCP
toolsconfigure_ai_provider(model / name / default only) /list_ai_providers/test_ai_provider/
delete_ai_provider.watch_event action_type=ai_replyrefuses creation when no connection is ready. - Docs:
docs/AUTONOMOUS_AI_REPLY.md(chain, states, config, device acceptance matrix). - NOT TESTED on device: the two-phone WhatsApp/Telegram run needs a stored provider key (none in CI).
#104 — one persistent notification; #133 B23/B26; #27 config-driven ignore list
NotificationPolicyis the single sanctioned poster: accessibility-off, wrong-PIN, recovery and health
alerts fold into the foreground notification; completion / schedule / morning-summary / AI-finish / Event
Hub posts go through the policy (userRequestedhonoured). Obsolete channels cleaned on upgrade.- Notification classification:
self,group_summary,otp,email,navigation/transport/
progress,message(MessagingStyle/CATEGORY_MESSAGE only), elsenotification. A Samsung Clock timer
is no longer a "message". Pre-upgrade watches namingmessagekeep their old catch-all (legacyMessage,
Room migration 1→2).read_notificationsandroute_action notifications_listshare one deduped inbox. - OTP privacy: codes masked (
••••) in stored/delivered payloads, logs, audit, history and the pending
inbox; per-watchinclude_codeopt-in exposes the live code in memory only. NotificationTriggerignore list is a setting (notification_trigger_ignored_packages), no hardcoded
AI-app list;.notify(baseline shrank from 14 keys to 6.
#133 / #110 / #105 — remaining device-QA defects
calendar create/list (writable calendar, instances, dedupe, ids), clipboard honesty (UNREADABLE_BACKGROUND,
verified read-back), max_chars exact, listen_speech hard timeout, exit_split_screen real display +
divider-drag fallback, structured mock-location results, get_ui_tree limits, interact_with_element
value/new_text, window-set verification token (IME/popup changes count), tap_text gesture fallback,
run_ui_test truthful verdicts, battery per-UID parsing, speakerphone route polling, update_contact name
parts, action-aware inbox counts, wake_screen verified + wake/keep_screen_on_actions settings,
ai_task_finished only on an explicit task_finished=true hint or a Task reaching DONE (ai_activity_idle
is the inferred signal).
#145 / #35 — automation backup + builder parity
Backup Export/Import via SAF (Merge / confirmed Replace; imported items paused until reviewed) wired to the
real watch/schedule/task stores; export_automations / import_automations / list_automations MCP tools;
device-side ParamSpec schemas for 20 common tools with a parity test against the relay; read-only
Automation Flow view (trigger → condition → steps) with scalar-parameter edits only.
#50 / #35 / #151 — queue, schedules, AI-active indicator
- Single owner of screen actions: every tool execution (relay, schedule, Event Hub, scripts, UI) goes
throughActionOwnerat the dispatcher chokepoint; screen-changing tools run one at a time in arrival
order, reads stay concurrent, cancel/management tools never wait. A 45 s bounded wait returns an honest
"Device busy with ; retry" instead of a ghost run.get_agent_diagnostics.action_ownerexposes it. - One durable FIFO on the existing TaskStore (
seq/priority/source/scheduledFor), a single
TaskQueueRunner, and one enqueue path: Event Hubtask/workflowwatches, scheduled fires and
execute_workflow async:trueall create queued tasks; crash recovery re-queues instead of resuming inline;
cancel_taskcancels only its own task. Newmanage_task(pause/resume/cancel/prioritize/replace);
list_tasksreports queue position and owner state; Tasks screen "Queue" section + "Pause queue"; the
floating panel shows the current task, step n/m, elapsed and the next three. - Schedules: per-schedule run history (scheduled vs actual time, outcome), start/end/max-runs bounds
(schedule_action,manage_scheduled_task update, backup round-trip), pause/resume per card, a guided
3-step create/edit flow with a live next-3 preview, TV-scaled focusable cards. - AI-active indicator: shown only while a tool actually executes on any path, hides immediately when the
setting is turned off, hidden during captures;ai_active_indicator_enabledis AI-configurable.
#29 / #174 / #111 / #114 / #23 / #12
- TV:
press_keyback/home verified against the foreground app,paste/focus_clickkeys, post-boot
accessibility repair via Shizuku (once per boot, never over a deliberate OFF). - Onboarding: persisted "skip", 24 h "Finish background setup" nudge, localized copy.
- Safety: one shared
CommitGuard(scope, UI-stable, target, recipient/payload binding, duplicate ledger)
used by learned skills, Teach replay, workflows,run_actionsand scripts; Teach replay stops after an
unverified side effect. - Learning:
learn_from_recordings_enabled(OFF) and an honestUNSUPPORTEDfor recording demonstrations;
docs distinguish recording / Teach macro / learned skill. - Self-hosting: LAN profile docs corrected (binds
${RELAY_LAN_IP}), Docker Desktop/WSL2 note, LAN-bind
regression test. - Fast Actions:
Unavailable/HandedOffoutcomes and a read-only AI fallback through a configured
provider connection; Quick command entry restored in Settings.
Testing
- Android CI (
android-ci.yml, one dispatch): unit tests + debug/androidTest build + relay typecheck. - Local pure-Kotlin harness (no Android SDK reachable from the build container): 900+ unit tests green.
- Relay: typecheck + 49 self-host tests green.
- Device E2E: NOT TESTED in this batch (owner-run; see each issue's acceptance matrix).
Touchless v0.2.320
Full Changelog: https://github.com/il90il90/touchless-releases/commits/v0.2.320
Touchless v0.2.319
Full Changelog: https://github.com/il90il90/touchless-releases/commits/v0.2.319
Touchless v0.2.318
Touchless v0.2.318 (code 328)
Issue: #112 — truthful rejection of unopenable navigation (continuing the destination-verification work).
Also: #23 — concrete LAN self-hosting troubleshooting docs.
#112 — don't claim success when nothing can open it
open_location_on_mapandnavigate_tonow check that a handler actually resolves on the device and
reject truthfully ("No app on this device handles 'geo:' links" / "…can start navigation") instead of
launching ageo:/google.navigation:intent into nothing and reporting "opened". When a map/navigation
app is installed, behaviour is unchanged.- Builds on the earlier #112 slice (v0.2.316):
launch_appdestination verdicts (foreground / wrong_app /
unverified) and the pureDestinationVerifier/EntryPointRegistry/UriSchemeSupporthelpers.
#23 — self-hosting LAN troubleshooting
docs/SELF_HOSTING.md gains concrete rows for the exact "localhost works, the phone on Wi-Fi can't reach it"
failure: starting the LAN compose profile, the Windows Defender inbound rule (Private only), Docker
Desktop/WSL2 port mapping, AP/client isolation, the cleartext opt-in, and why the no-token :8788 bridge stays
localhost-only. (The full Windows + LM Studio end-to-end proof still needs that physical setup.)
Testing
- PASS (Android CI): compile + unit tests green (the
UriSchemeSupport/DestinationVerifiersuites included). - NOT TESTED: physical-device E2E (owner's QA agent).
Touchless v0.2.317
Full Changelog: https://github.com/il90il90/touchless-releases/commits/v0.2.317
Touchless v0.2.316
Touchless v0.2.316 (code 326)
Issue: #112 — verify the actual destination of a navigation, not just that an app launched (first slice).
Honest destination verification for app launches
Launching an app now reports where it actually landed, instead of a vague "launched":
- foreground — the app really came to the front (VERIFIED).
- wrong_app — a different app or a chooser intercepted focus (so the AI knows it didn't land where it meant to).
- unverified — the foreground couldn't be read (e.g. Accessibility unbound); reported honestly, never a fake success.
Built on three pure, unit-tested helpers (reused by the broader navigation work to follow):
DestinationVerifier— classifies right-app/right-screen vs wrong-app/wrong-screen vs unreadable, with
package + sub-package/component matching.EntryPointRegistry— a per-app registry of discovered deep entry points, invalidated when the app changes
version so a stale entry is never reused.UriSchemeSupport— classifies a URI truthfully (web vs handler-scheme vs unsupported) so an unopenable
scheme (e.g.geo:with no handler) is rejected with a reason rather than launched into nothing.
Still to come on #112
Dynamic discovery of exported intents/app-links into the registry at runtime, destination verification wired
into open_url/navigate_to, and physical-device E2E. Tracked on the issue.
Testing
- PASS (Android CI): the three new pure unit suites (destination verdicts, registry staleness, URI classification).
- NOT TESTED: physical-device E2E (owner's QA agent).