M0: toolchain, monorepo scaffold, gateway plugin skeleton, CMP app
- gateway-plugin/: android platform plugin (plugin.yaml + adapter.py register(ctx) + no-op AndroidAdapter) + stub modules for M1-M5 - app/: Compose Multiplatform project (shared KMP + androidApp + desktopApp) with Gradle wrapper; builds :androidApp:assembleDebug and :desktopApp:compileKotlin - scripts/guard_hermes_agent.sh + pre-commit hook: fail if hermes-agent/ is staged (read-only reference, never committed) - .gitignore excludes hermes-agent/; docs/ reference library
This commit is contained in:
commit
59acf66c89
49 files changed
+3950
No files matched your search
@@ -0,0 +1,138 @@
|
||||
# 14 — Milestones (M0–M7)
|
||||
|
||||
Phased delivery. Each milestone ends with a **demo** (on-device where noted) and
|
||||
has explicit **acceptance criteria**. Work top-to-bottom; don't skip M0/M1.
|
||||
|
||||
---
|
||||
|
||||
## M0 — Toolchain & scaffolding
|
||||
**Goal:** everything builds; the plugin is discoverable; the repo is safe.
|
||||
- [ ] Install JDK 17, Android SDK, set `ANDROID_HOME` (`12-toolchain.md`).
|
||||
- [ ] `cd hermes-agent && uv sync` (hermes venv works).
|
||||
- [ ] Create monorepo scaffold (`02-monorepo.md`): `gateway-plugin/`, `app/`
|
||||
(CMP: `shared`, `androidApp`, `desktopApp`), root `.gitignore`
|
||||
(**excludes `hermes-agent/`**), root `README.md`.
|
||||
- [ ] `git init` + a pre-commit/CI guard that fails if `hermes-agent/` is staged.
|
||||
- [ ] CMP project builds empty: `./gradlew :androidApp:assembleDebug`,
|
||||
`./gradlew :desktopApp:run` (blank window).
|
||||
- [ ] Plugin skeleton: `plugin.yaml` + `adapter.py` with `register(ctx)` + a
|
||||
no-op `AndroidAdapter` → `hermes gateway status` lists **android**.
|
||||
- **Demo:** `hermes gateway status` shows `android`; `./gradlew
|
||||
:androidApp:installDebug` installs a blank app on the MIX 2S.
|
||||
- **Accept:** blank app installs + launches on-device; plugin visible in
|
||||
`hermes gateway status`; `hermes-agent/` is git-ignored (verify with
|
||||
`git status --ignored`).
|
||||
|
||||
## M1 — Gateway core loop (text round-trip)
|
||||
**Goal:** pair + send a text message + get a (non-streaming) reply.
|
||||
- [ ] WS server (`ws_server.py`): bind, `hello` auth (constant-time),
|
||||
`hello.ack`, heartbeat, connection registry.
|
||||
- [ ] `AndroidAdapter.send()` → `message` frame; inbound `message.send` →
|
||||
`MessageEvent` → `handle_message`.
|
||||
- [ ] Pairing store + `ANDROID_TOKEN`; QR payload in `interactive_setup`.
|
||||
- [ ] App: Connect screen (URL+token, real `hello` test), `GatewayClient`
|
||||
(connect + reconnect), ChatScreen sends + renders `message`.
|
||||
- [ ] `ws_probe.py` harness drives a real turn.
|
||||
- **Demo (on-device):** pair the phone, send "hello", see the agent's reply.
|
||||
- **Accept:** text round-trip works on-device; wrong token is rejected;
|
||||
reconnect after gateway restart re-pairs.
|
||||
|
||||
## M2 — Streaming + reasoning + tools + commentary
|
||||
**Goal:** the "agent transparency" features.
|
||||
- [ ] Map consumer `send`/`edit_message` → `message.start/update/stop`.
|
||||
- [ ] Reasoning: set `show_reasoning` for android; adapter splits prefix →
|
||||
`reasoning` field. **Verify format with `ws_probe.py`.**
|
||||
- [ ] Tool events: classify tool-progress `send()`s → structured
|
||||
`tool.start/progress/end` (turn-state machine). **Verify with probe.**
|
||||
- [ ] Commentary → `commentary` frames. Typing → `typing`.
|
||||
- [ ] App: live bubble (coalesced updates), `ReasoningBlock` (collapse + copy),
|
||||
`ToolCard` with **Everything/Truncated/Nothing** setting, dimmed
|
||||
`commentary` bubble.
|
||||
- **Demo (on-device):** a multi-step prompt streams, shows reasoning above the
|
||||
answer, tool cards (toggle verbosity), and an intermediate beat.
|
||||
- **Accept:** all four render correctly; tool verbosity setting changes
|
||||
rendering; reasoning copy button works; frame shapes match `04-wire-protocol`.
|
||||
|
||||
## M3 — Channels/threads + cron + search
|
||||
**Goal:** organization + cron delegation + search.
|
||||
- [ ] Channel directory (SQLite): default channel ensured; `channel.create/
|
||||
rename/set_default/delete` + `channel.*` frames.
|
||||
- [ ] Threads: toggle in default chat; `thread_id` lanes; `create_handoff_thread`.
|
||||
- [ ] `parse_target_ref_fn` + `cron_deliver_env_var` → cron
|
||||
`deliver=android:<chat>[:<thread>]` works.
|
||||
- [ ] `search.py` FTS5 bridge; `search` frame (all / this-chat) → results.
|
||||
- [ ] App: channel list (drawer/rail), thread toggle + topic switcher, "new
|
||||
channel" + "set as cron target", SearchScreen with scope toggle + jump.
|
||||
- **Demo (on-device):** create "Cron Reports"; create a cron job delivering to
|
||||
it; it fires into that channel; search finds a message (both scopes).
|
||||
- **Accept:** cron output lands in the chosen channel (not default); threads
|
||||
isolate context; search scoping correct; channel list reconciles on events.
|
||||
|
||||
## M4 — Media
|
||||
**Goal:** attach + receive + play media.
|
||||
- [ ] Inbound: `media.upload` chunked → `cache_*_from_bytes` → `media_urls`;
|
||||
size limit + sha256 + MIME re-sniff.
|
||||
- [ ] Outbound: `send_*` → `media.offer`; `media.pull` chunked; delivery-path
|
||||
security.
|
||||
- [ ] App: SAF pickers + preview chips + upload; `media.pull` → cache;
|
||||
**ExoPlayer** inline (audio mini-player, video fullscreen/PiP); image/doc
|
||||
viewers.
|
||||
- **Demo (on-device):** attach a photo + video (agent sees them); ask agent to
|
||||
send an image/video → plays live in-app.
|
||||
- **Accept:** both directions work; over-limit rejected; playback is live;
|
||||
only allowed files are servable.
|
||||
|
||||
## M5 — Push + offline (FCM + ntfy)
|
||||
**Goal:** reach the phone when backgrounded; catch up on reconnect.
|
||||
- [ ] Outbox (SQLite) + sync cursor; `sync`/`sync.done`; retention prune.
|
||||
- [ ] `push.py`: `FcmBackend` (HTTP v1 + service account, httpx) +
|
||||
`NtfyBackend`; selected by `ANDROID_PUSH_BACKEND`.
|
||||
- [ ] Fire push on no-live-subscriber; data payload for silent sync.
|
||||
- [ ] App: FCM service (foreground banner + background foreground-service sync),
|
||||
`onNewToken` → `fcm.register`; per-chat notification channels; deep-link.
|
||||
ntfy listener fallback.
|
||||
- [ ] In-app `notification` banners (channel_renamed, approval, cron, …).
|
||||
- **Demo (on-device):** background the app → trigger a message → notification
|
||||
appears → tap → syncs + opens the chat. Repeat with ntfy backend.
|
||||
- **Accept:** push arrives when backgrounded (both backends); reconnect syncs
|
||||
with no loss/dup; banners show for foreground events.
|
||||
|
||||
## M6 — Desktop app
|
||||
**Goal:** the same app on a big screen.
|
||||
- [ ] `desktopMain`: tray + OS notifications; `MediaPlayer` actual (mpv/WebView);
|
||||
`MediaPicker` actual (file dialog); `SecureStore` actual; window mgmt.
|
||||
- [ ] Two-pane default layout; keyboard shortcuts; optional inspector pane.
|
||||
- [ ] Parity pass vs Android feature checklist (`11-desktop-app.md`).
|
||||
- [ ] jpackage builds (Linux first; macOS/Windows as available).
|
||||
- **Demo:** desktop app pairs to the same gateway; full feature parity; tray
|
||||
notifications; media plays.
|
||||
- **Accept:** all Android features work on desktop; tray + shortcuts work;
|
||||
native binary launches.
|
||||
|
||||
## M7 — Polish + E2E + docs
|
||||
**Goal:** ship-quality.
|
||||
- [ ] Telegram-style layout pass (per reference image): header, bubbles, date
|
||||
separators, ✓✓, model/token footer, banner, bottom bar.
|
||||
- [ ] Theming (dark default, accent), onboarding/pairing UX, empty/loading/
|
||||
reconnecting/degraded states with honest copy.
|
||||
- [ ] Full E2E suite (`13-testing.md` scenarios 1–12) automated where possible.
|
||||
- [ ] Docs: `docs/protocol/frames.schema.json` finalized; `docs/setup.md`
|
||||
(user-facing pairing + FCM/ntfy + remote access); root README.
|
||||
- [ ] Security hardening checklist (`09-pairing-security.md`) verified.
|
||||
- **Demo:** end-to-end on phone + desktop simultaneously; cron into a channel;
|
||||
push; media; search.
|
||||
- **Accept:** all feature-checklist items pass on-device; E2E green; docs
|
||||
complete; `hermes-agent/` still never committed.
|
||||
|
||||
---
|
||||
|
||||
## Sequencing notes
|
||||
- **M1/M2 depend on the `ws_probe.py` harness** to lock frame shapes early —
|
||||
build it in M1.
|
||||
- **M3 (cron) and M5 (push) both touch the outbox** — build the outbox in M3,
|
||||
extend for push in M5.
|
||||
- **M6 (desktop) reuses M1–M5 shared code** — do it after the Android features
|
||||
are stable so the shared module is settled.
|
||||
- Parallelizable: plugin (Python) and app (Kotlin) can be worked on
|
||||
concurrently once the protocol (`04-wire-protocol.md`) is agreed; the probe
|
||||
harness is the integration seam.
|
||||
Reference in new issue
Block a user