Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1a95527
security audit
vahid-ai Mar 12, 2026
260f17e
Fix critical and high security audit findings
vahid-ai Mar 12, 2026
222083d
updated info
vahid-ai Mar 12, 2026
b98f795
Merge pull request #1 from vahid-ai/fix/security-audit-remediations
vahid-ai Mar 14, 2026
1f32eb6
Merge remote-tracking branch 'upstream/main'
vahid-ai Mar 15, 2026
e09351d
working base with termux
vahid-ai Mar 16, 2026
94e2e98
adding Shizuku
vahid-ai Mar 16, 2026
27c23fe
added debug modals in settings
vahid-ai Mar 16, 2026
7e99ec3
Fix Termux integration: PendingIntent FLAG_IMMUTABLE drops result ext…
vahid-ai Mar 17, 2026
24332f1
fixing Shizuku connection errors
vahid-ai Mar 17, 2026
6ae907f
Fix Shizuku+Termux diagnostic failing at adb privilege level
vahid-ai Mar 17, 2026
bf18e7a
Fix bottom UI elements covered by system navigation bar
vahid-ai Mar 17, 2026
9b2f6bd
Add Material Design 3 theme with black, blue, and white palette
vahid-ai Mar 18, 2026
6397096
Make all text, icons, and buttons blue to match primary color
vahid-ai Mar 18, 2026
fdcb42e
agent display notes
vahid-ai Mar 18, 2026
207bcfc
Add CI release workflow, fix release build, and document build/releas…
vahid-ai Mar 20, 2026
44140eb
Merge pull request #2 from vahid-ai/ben/ui-revamp
vahid-ai Mar 20, 2026
b94e99e
Fix release workflow version parsing for non-semver tags
vahid-ai Mar 20, 2026
e07d95d
Fix release workflow: semver tag filter and local.properties formatting
vahid-ai Mar 20, 2026
f5909a6
Fix release workflow: use JDK 21 JetBrains to match Gradle daemon too…
vahid-ai Mar 20, 2026
59d2d52
Fix CI build: increase Gradle heap to 4GB and build APKs sequentially
vahid-ai Mar 23, 2026
5762601
Add notes section to README linking agent display docs
vahid-ai Mar 23, 2026
9447c08
Merge pull request #3 from vahid-ai/ben/ui-revamp
vahid-ai Mar 23, 2026
a135ef3
Add custom OpenRouter model support in settings
vahid-ai Mar 23, 2026
56d766a
Merge pull request #4 from vahid-ai/ben/multi-model
vahid-ai Mar 23, 2026
159b1c1
Add local skill file upload from device storage
vahid-ai Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Build & Release APK

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
bump:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
prerelease:
description: 'Mark as pre-release'
required: false
default: false
type: boolean

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Determine version
id: version
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
# Get the latest semver tag (x.y.z), ignoring non-semver tags like v19
LATEST=$(git tag --list 'v*.*.*' --sort=-version:refname | head -n1)
LATEST=${LATEST:-v0.0.0}
# Strip the 'v' prefix and any pre-release suffix
VERSION=${LATEST#v}
VERSION=${VERSION%%-*}
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"

case "${{ inputs.bump }}" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac

NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
echo "tag=$NEW_TAG" >> "$GITHUB_OUTPUT"
echo "prerelease=${{ inputs.prerelease }}" >> "$GITHUB_OUTPUT"

# Create and push the tag
git tag "$NEW_TAG"
git push origin "$NEW_TAG"
else
# Triggered by tag push — use the tag as-is
echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
# Tags with a hyphen (e.g. v1.0.0-beta.1) are pre-releases
if [[ "${GITHUB_REF#refs/tags/}" == *-* ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi
fi

- name: Set up JDK 21 (JetBrains)
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'jetbrains'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Write local.properties
run: |
printf '%s\n' \
"BUNDLER_API=${{ secrets.BUNDLER_API }}" \
"ALCHEMY_API=${{ secrets.ALCHEMY_API }}" \
"PREMIUM_LLM_URL=${{ secrets.PREMIUM_LLM_URL }}" \
"ZEROX_API_KEY=${{ secrets.ZEROX_API_KEY }}" \
"BANKR_API=${{ secrets.BANKR_API }}" \
"OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" \
"TINFOIL_API_KEY=${{ secrets.TINFOIL_API_KEY }}" \
"OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" \
"VENICE_API_KEY=${{ secrets.VENICE_API_KEY }}" \
"CLAUDE_OAUTH_TOKEN=${{ secrets.CLAUDE_OAUTH_TOKEN }}" \
"TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}" \
"LLM_PROVIDER=${{ secrets.LLM_PROVIDER }}" \
"LLM_MODEL=${{ secrets.LLM_MODEL }}" \
"RELEASE_STORE_FILE=keystore.jks" \
"RELEASE_STORE_PASSWORD=${{ secrets.RELEASE_STORE_PASSWORD }}" \
"RELEASE_KEY_ALIAS=${{ secrets.RELEASE_KEY_ALIAS }}" \
"RELEASE_KEY_PASSWORD=${{ secrets.RELEASE_KEY_PASSWORD }}" \
> local.properties

- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/keystore.jks

- name: Build release APK
run: ./gradlew assembleRelease
env:
GRADLE_OPTS: -Xmx4g -Dfile.encoding=UTF-8

- name: Build debug APK
run: ./gradlew assembleDebug
env:
GRADLE_OPTS: -Xmx4g -Dfile.encoding=UTF-8

- name: Rename APKs for clarity
run: |
mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/AndyClaw-release.apk
mv app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/debug/AndyClaw-debug.apk

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
prerelease: ${{ steps.version.outputs.prerelease }}
files: |
app/build/outputs/apk/release/AndyClaw-release.apk
app/build/outputs/apk/debug/AndyClaw-debug.apk
generate_release_notes: true
131 changes: 131 additions & 0 deletions AGENT_DISPLAY_AND_SHIZUKU_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Agent Display / Virtual Display / Shizuku Notes

This document summarizes what `Agent Display` is, how it relates to `Agent Virtual Display`, what Android APIs are involved, and what works on stock Android with/without Shizuku.

---

## 1) What is **Agent Display** vs **Agent Virtual Display**?

### Agent Display (`AGENT_DISPLAY_API.md`)

`Agent Display` is the **framework Binder API surface** (`IAgentDisplayService`) that AndyClaw calls.

It defines methods for:

- Display lifecycle (`createAgentDisplay`, `destroyAgentDisplay`, `resizeAgentDisplay`, `getDisplayInfo`)
- App launch/control on the display
- Touch/gesture/key/text/clipboard input
- Screen capture (`captureFrame*`)
- Accessibility tree + node actions via proxy

Think of this file as the **contract**: what methods are available.

### Agent Virtual Display (`AGENT_VIRTUAL_DISPLAY.md`)

`Agent Virtual Display` is the **implementation architecture/design** behind the above API.

It explains:

- How to create/control a `VirtualDisplay`
- How to inject `MotionEvent`/`KeyEvent` style input
- How screenshot capture works with `ImageReader`
- Why auto-screenshot-after-every-action is useful for LLM loops
- Why permissions (`INJECT_EVENTS`) and execution context matter

Think of this file as the **how-to internals**.

---

## 2) Which Android APIs are used?

Core APIs/patterns used by the design:

- `DisplayManager#createVirtualDisplay(...)` + `VirtualDisplay`
- `ImageReader` (`newInstance`, `acquireLatestImage`) for capture
- `MotionEvent` (with display targeting via `displayId`)
- `InputManager.injectInputEvent(...)` for event injection (hidden/system API)
- `KeyEvent` + `KeyCharacterMap.getEvents()` for key/text paths
- `AccessibilityService` + `AccessibilityNodeInfo.performAction(...)` for tree/node actions

Important permission note from the design doc:

- `android.permission.INJECT_EVENTS` is signature-level.
- The doc recommends shell-context execution (`app_process`) for reliable injection when not platform-signed.

---

## 3) Can base/generic Android AndyClaw use Agent Display today?

Short answer: **No, not out-of-the-box.**

Why:

1. `AgentDisplaySkill` is privileged-only in this repo.
- OPEN tier manifest has no `agent_display` tools.
2. It depends on framework Binder service `"agentdisplay"`.
- If missing, it throws `AgentDisplayService not available`.
3. UI exposes Agent Display test controls only in privileged mode.

So on stock/generic Android builds, this exact feature path is unavailable unless the platform service/backend exists.

---

## 4) If Shizuku is running and connected, can we use this exact functionality?

Short answer: **Not the exact AgentDisplay binder feature.**

Shizuku gives ADB/shell-level command execution (very powerful), but it does **not** magically provide the framework `agentdisplay` Binder service.

So:

- **Exact `agent_display_*` feature path**: still unavailable without the platform service.
- **Practical automation alternative**: yes, many actions are still possible via shell commands (`input`, `am`, `wm`, `screencap`, etc.).

---

## 5) Recommended Shizuku fallback design (stock Android)

Goal: emulate useful parts of Agent Display without framework `agentdisplay`.

### Suggested tool surface

- `screen_screenshot`
- `screen_tap(x, y)`
- `screen_swipe(x1, y1, x2, y2, duration_ms)`
- `screen_key(keycode)`
- `screen_type(text)`
- `screen_launch(package/component)`
- (optional) `screen_ui_dump` via `uiautomator dump`

### Suggested implementation mapping

- Tap → `input tap x y`
- Swipe → `input swipe x1 y1 x2 y2 duration`
- Key → `input keyevent <code>`
- Type → `input text <escaped>`
- Launch → `am start ...`
- Screenshot → `screencap -p` (binary output path preferred)

### Suggested orchestration loop

1. Capture initial screenshot
2. Ask model for one action
3. Execute action via Shizuku
4. Wait for UI settle (roughly 300–1000ms depending on action)
5. Capture screenshot again
6. Repeat until complete or iteration cap reached

### Known tradeoffs vs true Agent Display

- Controls the active/main display (no isolated virtual display)
- Less deterministic than targeted `displayId` event injection
- Still effective for many practical automation tasks

---

## 6) Implementation note for this repo

If implementing fallback here, use a separate stock-Android skill (e.g. `ShizukuDisplaySkill`) and keep existing privileged `AgentDisplaySkill` unchanged.

One technical caveat: current `ShizukuManager.executeCommand()` is text-oriented and truncates output; screenshot capture is better handled through a binary streaming path.

Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,37 @@ class ClawHubManager(
}
}

// ── Local import ────────────────────────────────────────────────

/**
* Import a SKILL.md from local device storage (e.g. file picker).
*
* Writes the content to `managedSkillsDir/<slug>/SKILL.md`, records
* the install in the lockfile, and reloads the skill registry so the
* skill becomes immediately available — the same path as a ClawHub
* download.
*
* @param slug Unique slug for the skill (derived from frontmatter name).
* @param content Raw SKILL.md content.
* @return Result describing success or failure.
*/
fun importLocalSkill(slug: String, content: String): InstallResult {
val targetDir = File(managedSkillsDir, slug)
targetDir.mkdirs()

val skillMd = File(targetDir, "SKILL.md")
return try {
skillMd.writeText(content)
lockFile.recordInstall(slug, "local")
reloadSkillRegistry()
log.info("Imported local skill '$slug'")
InstallResult.Success(slug, "local")
} catch (e: Exception) {
log.warning("Failed to import local skill '$slug': ${e.message}")
InstallResult.Failed(slug, e.message ?: "Unknown error")
}
}

// ── Internals ───────────────────────────────────────────────────

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ fun buildGatewayTlsConfig(
return GatewayTlsConfig(
sslSocketFactory = context.socketFactory,
trustManager = trustManager,
hostnameVerifier = HostnameVerifier { _, _ -> true },
hostnameVerifier = if (expected != null || params.allowTOFU) {
// When pinning by fingerprint or TOFU, hostname may not match the cert CN/SAN
// (e.g. self-signed gateway certs). The fingerprint check in the TrustManager
// already authenticates the server, so hostname verification is redundant.
HostnameVerifier { _, _ -> true }
} else {
// Default path: cert is validated by the platform CA store, so enforce hostname.
javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier()
},
)
}

Expand Down
Loading