Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,12 @@ package manifests before running repository JavaScript or package scripts. Load
## Security Baseline

- Never log tokens, credentials, API keys, authentication headers, cookies, or webhook secrets. Use `redactSensitiveHeaders` when headers must be retained or logged. Do not enable `sendDefaultPii` or `attachRpcInput` in Sentry.

## Kilobot Review Remarks

When addressing a Kilobot review remark:

1. Verify that the remark is valid. If unsure, attempt to reproduce it with an E2E or unit test.
2. If valid, fix it and commit the change. Prefer small commits in general.
3. Reply in the review thread, then resolve the thread.
4. Push the commits.
12 changes: 7 additions & 5 deletions apps/mobile/.kilo/agent/mobile-e2e-verifier.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Verifies an approved mobile-app change end to end against local services, simulator, CLI, and related repositories
description: Verifies an approved mobile change end to end
mode: subagent
model: kilo/kilo-auto/efficient
steps: 60
steps: 100
permission:
edit: deny
external_directory: allow
Expand All @@ -21,12 +21,14 @@ Before testing:

1. Read `apps/mobile/e2e/AGENTS.md` and all instructions it references.
2. Translate the orchestrator's acceptance criteria into observable happy, retryable unhappy, non-retryable unhappy, and empty flows for every new user-facing feature.
3. Record pre-existing services, simulators, and tmux sessions so cleanup only removes resources you create.
3. Record pre-existing services, simulators, and tmux sessions so cleanup only removes resources you create. Claim a simulator with `pnpm dev:mobile:simulator claim`; never use one claimed by another worktree.

During verification:

- Verify the app is running the bundle and services from the intended worktree.
- Inspect the current screen before selecting Maestro elements and re-inspect after UI changes.
- Run the login/logout helper preflight rather than manually inferring bundle provenance, ports, or simulator ownership.
- Run `pnpm dev:mobile:android doctor` before declaring Android tooling unavailable; do not rely on the inherited `PATH`.
- Use Maestro as the primary driver on iOS and Android. Fall back to `xcrun simctl` on iOS or repository-wrapped ADB on Android only when Maestro cannot inspect or operate the state, or when low-level device control is required.
- Inspect the current screen before selecting Maestro elements and re-inspect after UI changes. Copy exact hierarchy text; never infer selectors from screenshots or visible tab captions.
- Exercise every applicable feature state that can be produced safely and deterministically. A skipped state requires an explicit rationale; do not silently omit it.
- Confirm retryable and empty states show a meaningful message and actionable CTA, and that the CTA performs the expected recovery or next step.
- Confirm non-retryable states show a meaningful message with no CTA at all.
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/.kilo/agent/mobile-implementer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
description: Implements an approved mobile-app plan, including required changes in cloud services, shared packages, or sibling repositories
mode: subagent
model: kilo/kilo-auto/efficient
steps: 50
steps: 80
permission:
edit: allow
external_directory: allow
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/.kilo/agent/mobile-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
description: Reviews an implementation produced for an approved mobile-app plan, including cross-repository changes
mode: subagent
model: kilo/kilo-auto/efficient
steps: 35
steps: 50
permission:
edit: deny
external_directory: allow
Expand Down
82 changes: 53 additions & 29 deletions apps/mobile/e2e/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Use this runbook for interactive verification against a local backend. Commands

## Fresh Worktree Quickstart

If dependencies or local env files are missing, run this once:
If dependencies or local env files are missing, run this once. This also authorizes both worktree `.envrc` files and copies local env files from the primary checkout:

```bash
node --version # must be v24; activate the root .nvmrc first if needed
Expand All @@ -21,52 +21,48 @@ xcrun simctl list devices booted

Reuse a complete stack already running for this worktree. Do not start a competing stack or stop an unrelated `kilo-dev-*` session.

When this worktree has no stack, start the complete mobile flow with worktree-safe ports:
`kiloclaw-docker-tcp` on port 23750 is the sole intentional host-wide exception: it is a stateless loopback proxy to the same Docker socket. The runner reuses an occupied proxy. Never kill a `socat` process owned by another worktree to free this port.

When this worktree has no stack, start the complete mobile flow. Secondary worktrees automatically receive an isolated port offset, and mobile startup generates and explicitly injects this worktree's LAN URLs before Metro starts. Do not export `KILO_PORT_OFFSET` or source `apps/mobile/.env`; stale parent-shell values must not select the bundle endpoints:

```bash
export KILO_PORT_OFFSET=auto
pnpm dev:env -y cloudflare-session-ingest
pnpm dev:start --no-attach mobile cloud-agent-next kiloclaw event-service
pnpm drizzle migrate
pnpm dev:env:mobile
pnpm dev:restart nextjs
pnpm dev:restart mobile
sleep 3
pnpm dev:status --json
```

`dev:env:mobile` writes this stack's LAN URLs. Restart Next.js for native auth callbacks and Metro for bundled Expo env values. The session-ingest env step creates the JWT Secrets Store binding; without it the worker can appear healthy while rejecting every session request. `event-service` is required for presence and notification behavior.
The session-ingest env step creates the JWT Secrets Store binding; without it the worker can appear healthy while rejecting every session request. `event-service` is required for presence and notification behavior.

Secrets Store state is local to each Worker directory. `dev:start` runs env sync for its selected service graph and refreshes every source-backed secret before launching Workers; secret creation failures are fatal. Do not run bare `wrangler secrets-store` commands: use `pnpm dev:env -y <group>` from the repository root so values come from the canonical local source and Wrangler receives a complete non-interactive prompt.

Confirm `mobile`, `nextjs`, `cloudflare-session-ingest`, `cloud-agent-next`, `kiloclaw`, and `event-service` are `up`. Restarts are asynchronous; if `mobile` is still starting, inspect its log and rerun status instead of restarting the stack. Use reported ports; never assume defaults in a secondary worktree.

## Logs and tmux

Prefer the stable log files:
Prefer the stable log files. When pane output is required, let the runner find the service wherever the dashboard moved it:

```bash
pnpm dev:status --json
tail -n 200 dev/logs/<service>.log
pnpm dev:capture mobile
```

Use tmux when you need live pane state or an interactive process:
Do not guess a tmux window from `tmux ls` or use `<session>:<service>` directly; a service pane can be joined into the dashboard and no longer have a same-named window. Use `pnpm dev:capture <service>` for inspection. Use raw tmux only for an interactive process after reading the exact `session` from `pnpm dev:status --json` and resolving the pane with `tmux list-panes -a`.

```bash
tmux ls
tmux list-windows -t <dev-session>
tmux capture-pane -p -t <dev-session>:<service> -S -200
```

Put any extra long-lived CLI, recorder, or log follower in a clearly named `kilo-e2e-*` tmux session so it is visible and easy to remove.

## iOS Simulator

Reuse a booted simulator when possible, but always replace any installed Kilo app when validating a fresh worktree. This avoids stale native builds, most app data, and saved Metro URLs. iOS Keychain credentials can survive uninstall; the login helper handles an existing session. Otherwise boot one explicitly:
Never share a simulator with another worktree. Claim one before any build, install, login, Maestro, or MCP action; the command prefers an unclaimed shutdown device and boots it. Pass a UDID only when intentionally claiming that device:

```bash
xcrun simctl list devices available
xcrun simctl boot <udid>
open -a Simulator
xcrun simctl bootstatus <udid> -b
pnpm dev:mobile:simulator claim [udid]
```

Uninstall Kilo, then build from this worktree in a dedicated tmux session. A fresh checkout generates the gitignored `ios/` directory and installs pods, so the first build takes a few minutes and is noisy:
Expand All @@ -89,18 +85,20 @@ xcrun simctl openurl <udid> \
"exp+kilo-app://expo-development-client/?url=http%3A%2F%2F<lan-ip>%3A<metro-port>"
```

Before testing, capture the `mobile` pane and verify `Starting project at <this-worktree>/apps/mobile` plus a fresh `iOS Bundled` line. Seeing the Kilo login screen alone does not prove bundle provenance. The login helper dismisses the clean-install tracking alert and Expo dev-menu introduction when present.
Before testing, capture the `mobile` pane and verify `Starting project at <this-worktree>/apps/mobile` plus a fresh `iOS Bundled` line. Seeing the Kilo login screen alone does not prove bundle provenance. The login preflight also reads Metro's development manifest and verifies `expoConfig.extra.apiBaseUrl` and `_internal.projectRoot` against this worktree. These endpoint extras come from the Metro manifest in a dev client; after env changes, regenerate env, restart Metro, reconnect the dev client to the exact Metro URL, and reload. Rebuild only when native config/plugins changed. The login helper dismisses the clean-install tracking alert and Expo dev-menu introduction when present.

## Sign In and Out

Backend and Metro must be running. These idempotent wrappers leave the app in a known state:
Backend and Metro must be running. These idempotent wrappers first verify simulator ownership, this worktree's required services, generated API port, and Metro project provenance, then reconnect the dev client to that exact Metro URL before launch. Do not bypass their preflight or call the YAML login steps directly:

```bash
apps/mobile/e2e/login.sh <udid> [email] # default: e2e-mobile@example.com
apps/mobile/e2e/logout.sh <udid>
```

Login requests an email OTP, reads it from `dev/logs/emails/`, verifies it, and accepts first-account consent. Re-run login if a dev-build cold launch loses the session.
Login requests an email OTP, waits up to 30 seconds for the worktree-local outbox, verifies it, accepts first-account consent, and asserts Home. It retries only the known dev-client launch/request boundary once. The wrappers use preflight to open the exact dev-client URL, then `flows/settle-app.yaml` handles late tracking and Expo developer-menu prompts without restarting the app. `flows/open-app.yaml` remains the standalone cold-launch flow.

Native prompts are states in the flow, not errors to tap through blindly. The shared launch flow recognizes the iOS tracking prompt (`Allow “Kilo” to track your activity across other companies’ apps and websites?`) and chooses `Ask App Not to Track`; login handles notification permission after authentication. Feature-triggered prompts such as speech recognition and microphone access must be handled only when the acceptance flow reaches that feature: inspect the hierarchy, copy the exact button accessibility text (`Allow` or `Don’t Allow`), and choose the state required by the test. Never use a generic `tapOn: 'Allow'` before identifying which prompt is visible.

Maestro can emit a large interactive transcript. For agent-driven runs, keep successful output out of context and show only a bounded failure tail:

Expand Down Expand Up @@ -132,7 +130,11 @@ One-time machine setup:
brew install maestro
```

For MCP use stdio command `maestro mcp`, then restart the agent session so its tools appear. Inspect the screen before selecting elements and re-inspect after UI changes. Flows use `appId: com.kilocode.kiloapp`; accessibility labels match with `text:`, not `id:`.
For MCP use stdio command `maestro mcp`, then restart the agent session so its tools appear. Inspect the screen before selecting elements and re-inspect after UI changes. Never guess a selector from the visible label or screenshot: copy the exact `txt` or `a11y` value from `maestro_inspect_screen`, mapping `a11y` to Maestro `text:`. Maestro text matching is full-string regex, not substring matching.

Use Maestro as the primary iOS automation driver. Fall back to `xcrun simctl` only when Maestro cannot inspect or operate a native state, or when low-level simulator control is required. iOS setup still uses `simctl` for boot, install/uninstall, dev-client URL reconnection, screenshots, shutdown, and cleanup.

Tab buttons are exposed through React Navigation's full accessibility labels, not the visible uppercase text. Current iOS labels are `Home, tab, 1 of 4`, `KiloClaw, tab, 2 of 4`, `Agents, tab, 3 of 4`, and `Profile, tab, 4 of 4`. `tapOn: 'Agents'` is wrong. Inspect again before using these examples because tab count or labels can change.

CLI fallback:

Expand Down Expand Up @@ -184,21 +186,41 @@ The mobile list updates after the CLI WebSocket connects and its first heartbeat

## Android Emulator

Follow the [Expo environment guide](https://docs.expo.dev/get-started/set-up-your-environment/) for the one-time Android SDK/JDK setup. This project needs JDK 17. With an AVD already created, keep the emulator in its own tmux session:
Do not conclude that Android is unavailable from `command -v adb` or the agent's inherited `PATH`. The repository resolves the SDK and JDK 17 from `ANDROID_HOME`, `~/Library/Android/sdk`, and standard Homebrew locations. Run the doctor first; it prints resolved absolute paths and available AVDs:

```bash
pnpm dev:mobile:android doctor
```

Use the wrappers for all Android tooling so the resolved SDK/JDK environment is applied, including the Expo/Gradle build:

```bash
export JAVA_HOME=$(/usr/libexec/java_home -v 17)
ANDROID_SESSION="kilo-e2e-android-$(basename "$PWD")"
tmux new-session -d -s "$ANDROID_SESSION" \
"JAVA_HOME=$JAVA_HOME emulator -avd <avd-name> -no-snapshot-save -no-boot-anim -gpu swiftshader_indirect"
adb wait-for-device
adb reverse tcp:<nextjs-port> tcp:<nextjs-port>
adb reverse tcp:<metro-port> tcp:<metro-port>
tmux new-session -d -s "$ANDROID_SESSION" -c "$PWD" \
"pnpm dev:mobile:android emulator -avd <avd-name> -no-snapshot-save -no-boot-anim -gpu swiftshader_indirect"
pnpm dev:mobile:android adb wait-for-device
pnpm dev:mobile:android claim <serial>
pnpm dev:mobile:android adb reverse tcp:<nextjs-port> tcp:<nextjs-port>
pnpm dev:mobile:android adb reverse tcp:<metro-port> tcp:<metro-port>
cd apps/mobile
npx expo run:android --no-install --no-bundler
pnpm -w dev:mobile:android build
```

Android's `localhost` is the emulator, so restore both `adb reverse` mappings after clearing app data. The dev-client scheme is `exp+kilo-app`. `adb shell pm clear com.kilocode.kiloapp` also forgets the Metro URL, so re-open the dev-client URL afterward.
Use Maestro as the primary Android automation driver, matching iOS. Fall back to repository-wrapped ADB when Maestro cannot inspect or operate a native prompt, when direct intent/process control is required, or when diagnosing the emulator itself. Android setup still uses ADB for readiness and port reversal. Use the repository wrapper rather than bare `adb`:

```bash
pnpm dev:mobile:android adb devices -l
pnpm dev:mobile:android adb -s <serial> shell uiautomator dump /sdcard/window.xml
pnpm dev:mobile:android adb -s <serial> shell cat /sdcard/window.xml
pnpm dev:mobile:android adb -s <serial> exec-out screencap -p > /tmp/kilo-android.png
pnpm dev:mobile:android adb -s <serial> shell input tap <x> <y>
pnpm dev:mobile:android adb -s <serial> shell input text '<text>'
pnpm dev:mobile:android adb -s <serial> shell input keyevent KEYCODE_BACK
```

Derive tap coordinates from the current `uiautomator` bounds, not screenshots or remembered positions. Re-dump after every navigation or prompt. Android's `localhost` is the emulator, so restore both `adb reverse` mappings after clearing app data. The dev-client scheme is `exp+kilo-app`. `adb shell pm clear com.kilocode.kiloapp` also forgets the Metro URL, so re-open the dev-client URL afterward with `adb shell am start`.

The existing login/logout helpers accept either an iOS simulator UDID or an Android ADB serial. Their shared preflight applies platform-specific device ownership and reconnects the dev client to this worktree before Maestro runs.

## Cleanup

Expand All @@ -212,6 +234,8 @@ rm -rf "$CLI_SCRATCH" # if created
rm -f "$LOGIN_LOG" # if created
pnpm dev:stop # only if you started this worktree's stack
xcrun simctl shutdown <udid> # only if you booted it
pnpm dev:mobile:simulator release <udid> # release every simulator you claimed
pnpm dev:mobile:android release <serial> # release every Android device you claimed
```

Also stop recorders, log followers, and emulator processes you created. Never use `tmux kill-server`, kill unrelated `kilo-dev-*` sessions, stop a simulator that was already booted, or use `pnpm dev:stop --force` while sibling worktrees are active.
Expand Down
62 changes: 11 additions & 51 deletions apps/mobile/e2e/flows/login-request-code.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Login step 1/2 — request an email sign-in code.
#
# Fresh-launches the app and, if signed out, submits EMAIL to reach the
# "Enter the code" screen. The 6-digit code is emailed to the local outbox
# Establishes a signed-out baseline, then submits EMAIL to reach the "Enter the
# code" screen. The 6-digit code is emailed to the local outbox
# (dev/logs/emails/); read it there and pass it to login-verify-code.yaml.
#
# maestro --device <udid> test -e EMAIL=e2e-mobile@example.com \
Expand All @@ -14,53 +14,13 @@
# code for you. See e2e/AGENTS.md ("Login / logout helper flows").
appId: com.kilocode.kiloapp
---
# A clean iOS install can put system and Expo onboarding over the app. Clear
# both before returning to SpringBoard; otherwise the Kilo icon is not tappable.
- runFlow:
when:
visible: 'Ask App Not to Track'
commands:
- tapOn: 'Ask App Not to Track'
- runFlow:
when:
visible: 'This is the developer menu.*'
commands:
- tapOn: 'Continue'
- stopApp
- tapOn:
text: 'Kilo'
# Three possible launch states: Home (signed in), the consent gate (signed in,
# not yet consented), or the login page (signed out).
- runFlow: logout.yaml
Comment thread
iscekic marked this conversation as resolved.
# Tap the input via its placeholder — the "Email address" label shares the
# field's accessibility text and would match the (non-focusable) label.
- tapOn: 'you@example.com'
- inputText: ${EMAIL}
- tapOn: 'Send sign-in code'
# Wait for the code screen — sending is a network round-trip.
- extendedWaitUntil:
visible:
text: 'HOME|Home, tab, 1 of 4|Welcome to Kilo Code|Accept and continue'
timeout: 30000
# Grant the notification permission prompt if iOS shows one on first launch.
- runFlow:
when:
visible: 'Allow'
commands:
- tapOn: 'Allow'
# Booted into the consent gate (already signed in) -> accept it.
- runFlow:
when:
visible: 'Accept and continue'
commands:
- tapOn: 'Accept and continue'
- extendedWaitUntil:
visible: 'HOME|Home, tab, 1 of 4'
timeout: 15000
# Signed out -> submit the email to trigger the code.
- runFlow:
when:
visible: 'Welcome to Kilo Code'
commands:
# Tap the input via its placeholder — the "Email address" label shares the
# field's accessibility text and would match the (non-focusable) label.
- tapOn: 'you@example.com'
- inputText: ${EMAIL}
- tapOn: 'Send sign-in code'
# Wait for the code screen — sending is a network round-trip.
- extendedWaitUntil:
visible: 'Verify code'
timeout: 15000
visible: 'Verify code'
timeout: 15000
Loading
Loading