From 50b72593bb1e9f34a14cc189ba23fd76fc89d16a Mon Sep 17 00:00:00 2001 From: Arul Sharma <31745423+arul28@users.noreply.github.com> Date: Wed, 20 May 2026 14:37:01 -0400 Subject: [PATCH 1/6] feat(macos-vm): dedicated /vm tab with lifecycle, onboarding, runtime install, and credentials Move the macOS VM workflow out of the inline lane panel into a dedicated /vm tab. Add lifecycle/menu UI (MacVmPage, VmLifecycleMenu, FirstBootCard, CredentialsPromptDialog, PhaseStepper), a credentials store, ADE runtime bootstrap for guest VMs, and recovery for stale guest-created worktree dirs. Wire 20+ macos-vm adeActions and CLI subcommands; update docs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .agents/skills/plan/SKILL.md | 175 ++ .agents/skills/source-command-audit/SKILL.md | 26 + apps/ade-cli/README.md | 4 + apps/ade-cli/src/cli.ts | 329 ++- .../src/tuiClient/__tests__/Drawer.test.tsx | 52 +- .../src/tuiClient/components/Drawer.tsx | 34 +- apps/desktop/package-lock.json | 7 + apps/desktop/package.json | 1 + .../agent-skills/ade-macos-vm/SKILL.md | 107 +- apps/desktop/src/main/main.ts | 141 +- .../src/main/services/adeActions/registry.ts | 2 +- .../main/services/chat/agentChatService.ts | 53 + .../src/main/services/ipc/ipcTimeouts.test.ts | 29 +- .../src/main/services/ipc/ipcTimeouts.ts | 51 +- .../src/main/services/ipc/registerIpc.ts | 198 +- .../main/services/ipc/runtimeBridge.test.ts | 30 + .../services/lanes/laneLaunchContext.test.ts | 226 +- .../main/services/lanes/laneLaunchContext.ts | 217 +- .../main/services/lanes/laneService.test.ts | 36 + .../src/main/services/lanes/laneService.ts | 379 +++- .../main/services/macosVm/credentialsStore.ts | 193 ++ .../main/services/macosVm/macosVmRecovery.ts | 208 ++ .../services/macosVm/macosVmService.test.ts | 1784 +++++++++++++-- .../main/services/macosVm/macosVmService.ts | 1974 ++++++++++++++++- .../main/services/macosVm/rfbDirectClient.ts | 134 +- .../main/services/macosVm/runtimeBootstrap.ts | 274 +++ .../remoteRuntime/remoteConnectionPool.ts | 28 + apps/desktop/src/main/services/state/kvDb.ts | 2 + apps/desktop/src/preload/global.d.ts | 27 + apps/desktop/src/preload/preload.test.ts | 101 + apps/desktop/src/preload/preload.ts | 72 +- apps/desktop/src/renderer/browserMock.ts | 10 + .../src/renderer/components/app/App.tsx | 9 + .../src/renderer/components/app/AppShell.tsx | 5 +- .../src/renderer/components/app/TabNav.tsx | 32 +- .../chat/AgentChatPane.submit.test.tsx | 10 +- .../lanes/CreateLaneDialog.test.tsx | 155 ++ .../components/lanes/CreateLaneDialog.tsx | 215 +- .../components/lanes/LanesPage.test.ts | 146 +- .../renderer/components/lanes/LanesPage.tsx | 331 ++- .../terminals/MacosVmPanel.test.tsx | 223 -- .../components/terminals/MacosVmPanel.tsx | 599 ----- .../components/terminals/WorkSidebar.test.tsx | 29 +- .../components/terminals/WorkSidebar.tsx | 30 - .../terminals/WorkStartSurface.test.tsx | 33 + .../components/terminals/WorkStartSurface.tsx | 11 +- .../components/terminals/WorkViewArea.tsx | 60 +- .../renderer/components/ui/TabBackground.tsx | 6 +- .../components/vm/CredentialsPromptDialog.tsx | 170 ++ .../components/vm/CurrentVmLaneRow.tsx | 75 + .../renderer/components/vm/FirstBootCard.tsx | 246 ++ .../renderer/components/vm/MacMiniGlyph.tsx | 67 + .../components/vm/MacVmComingSoon.tsx | 181 ++ .../renderer/components/vm/MacVmPage.test.tsx | 401 ++++ .../src/renderer/components/vm/MacVmPage.tsx | 1540 +++++++++++++ .../renderer/components/vm/PhaseStepper.tsx | 175 ++ .../components/vm/VmLifecycleMenu.tsx | 122 + apps/desktop/src/renderer/index.css | 55 + .../renderer/lib/macosVmRuntimeReadiness.ts | 185 ++ .../renderer/lib/visualContextFormatting.ts | 10 +- .../src/renderer/lib/workPtyContextEvents.ts | 1 - .../src/renderer/state/appStore.test.ts | 4 +- apps/desktop/src/renderer/state/appStore.ts | 13 +- apps/desktop/src/renderer/types/novnc.d.ts | 24 + apps/desktop/src/shared/adeCliGuidance.ts | 4 +- apps/desktop/src/shared/ipc.ts | 8 + apps/desktop/src/shared/types/lanes.ts | 4 + apps/desktop/src/shared/types/macosVm.ts | 277 ++- apps/ios/ADE/Resources/DatabaseBootstrap.sql | 3 + docs/ARCHITECTURE.md | 9 +- docs/features/ade-code/README.md | 2 +- docs/features/computer-use/README.md | 85 +- docs/features/lanes/README.md | 2 +- .../features/terminals-and-sessions/README.md | 23 +- .../terminals-and-sessions/ui-surfaces.md | 5 +- docs/perf/macos-vm-tab-action-inventory.md | 344 +++ docs/perf/work-tab-action-inventory.md | 49 +- 77 files changed, 11524 insertions(+), 1358 deletions(-) create mode 100644 .agents/skills/plan/SKILL.md create mode 100644 .agents/skills/source-command-audit/SKILL.md create mode 100644 apps/desktop/src/main/services/macosVm/credentialsStore.ts create mode 100644 apps/desktop/src/main/services/macosVm/macosVmRecovery.ts create mode 100644 apps/desktop/src/main/services/macosVm/runtimeBootstrap.ts create mode 100644 apps/desktop/src/renderer/components/lanes/CreateLaneDialog.test.tsx delete mode 100644 apps/desktop/src/renderer/components/terminals/MacosVmPanel.test.tsx delete mode 100644 apps/desktop/src/renderer/components/terminals/MacosVmPanel.tsx create mode 100644 apps/desktop/src/renderer/components/vm/CredentialsPromptDialog.tsx create mode 100644 apps/desktop/src/renderer/components/vm/CurrentVmLaneRow.tsx create mode 100644 apps/desktop/src/renderer/components/vm/FirstBootCard.tsx create mode 100644 apps/desktop/src/renderer/components/vm/MacMiniGlyph.tsx create mode 100644 apps/desktop/src/renderer/components/vm/MacVmComingSoon.tsx create mode 100644 apps/desktop/src/renderer/components/vm/MacVmPage.test.tsx create mode 100644 apps/desktop/src/renderer/components/vm/MacVmPage.tsx create mode 100644 apps/desktop/src/renderer/components/vm/PhaseStepper.tsx create mode 100644 apps/desktop/src/renderer/components/vm/VmLifecycleMenu.tsx create mode 100644 apps/desktop/src/renderer/lib/macosVmRuntimeReadiness.ts create mode 100644 apps/desktop/src/renderer/types/novnc.d.ts create mode 100644 docs/perf/macos-vm-tab-action-inventory.md diff --git a/.agents/skills/plan/SKILL.md b/.agents/skills/plan/SKILL.md new file mode 100644 index 000000000..fe1d124b8 --- /dev/null +++ b/.agents/skills/plan/SKILL.md @@ -0,0 +1,175 @@ +--- +name: plan +description: Deliberate a feature or change in three locked rounds — functional requirements, then UI design with wireframes, then extras/quirks/out-of-the-box ideas. Each round asks the user clarifying questions via AskUserQuestion before proceeding. New functional scope at any point cascade-restarts the new piece through all three rounds and merges it into the locked plan. Use when the user invokes `/plan`, when the user is in plan mode and asks for a design/spec/feature breakdown, or when a non-trivial change needs structured deliberation before implementation. +metadata: + author: ade + version: "1.0" +--- + +# /plan — Three-Round Locked Deliberation + +A feature plan has three layers: **what it does**, **how it looks**, and **the delightful extras**. Cross-talk between them produces sloppy plans. This skill enforces a strict order: lock functional, then lock UI, then add extras. New functional scope discovered at any point cascades back through all three rounds *for that new piece only*, then merges into the locked plan. + +## Activation + +Activate when **any** of: +- The user invokes `/plan` (with or without extra context). +- The user is in plan mode and asks for a design, spec, breakdown, or feature plan. +- A non-trivial change request would benefit from structured deliberation (multi-component features, UX-sensitive work, anything spanning >2 files). + +## Pre-flight: plan mode gate + +Before Round 1, verify plan mode is active. If not: + +> This skill is meant to run in plan mode (read-only deliberation). Enter plan mode (Shift+Tab cycles to it) and re-invoke `/plan `. + +Then stop. Do not proceed in edit/full-auto modes — the deliberation contract assumes no files will be touched mid-plan. + +## State you must track + +Maintain these in your head (or in scratch) across the whole skill: + +- **LockedFunctional** — bullet list of functional requirements confirmed so far. +- **LockedUI** — bullet list of UI decisions confirmed so far (with wireframe sketches). +- **LockedExtras** — list of delightful extras confirmed. +- **CurrentRound** — 1 (Functional), 2 (UI), or 3 (Extras). +- **PendingNewPieces** — queue of new functional requirements detected mid-flight that need their own cascade. + +When a cascade fires, you snapshot CurrentRound, run the cascade for the new piece, merge results back into the Locked* sets, then resume from the snapshotted round. + +## Round 1 — Functional Requirements + +Silently deliberate on what the user asked for. Pull out: +- Core capabilities the feature must have. +- Boundaries (what it does *not* do). +- Inputs, outputs, edge cases that change behavior. +- Ambiguities where multiple interpretations exist. + +Then call **AskUserQuestion** with 1–4 questions that resolve the meaningful ambiguities. Each option should describe a concrete interpretation with its trade-off. Avoid asking the user to write prose — frame everything as concrete choices. + +### Tool reference: AskUserQuestion + +Use `AskUserQuestion(questions)` to gather structured choices during Round 1, Round 2, Round 3, and any cascade. `questions` is an array of question objects: + +- `id`: stable snake_case identifier. +- `title`: short user-facing label. +- `text`: the actual question. +- `multiSelect`: optional boolean for menus like LockedExtras. +- `allowOther`: optional boolean that permits an "Other" selection. +- `allowAnnotation`: optional boolean that permits free-text annotation alongside a selected option. +- `options`: array of `{ id, label, tradeoffs, description?, preview? }`. + +The return value is keyed by question id and contains `selectedOptionIds`, optional `otherText`, and optional `annotations` / `freeText`. Treat selected option ids as the locked choice. Treat `otherText` as a new option authored by the user; if it changes behavior, update **LockedFunctional** and trigger the **Cascade rule**. Treat annotations as refinements to the selected option; if an annotation adds behavior rather than clarifying wording or presentation, it also cascades. + +When the user responds: +- Treat selected options as additions to **LockedFunctional**. +- If the user's free-text (the "Other" reply or annotation) adds new scope → see **Cascade rule** below. +- If the user only clarifies an existing item → update **LockedFunctional** and proceed. + +Once questions are answered, **do not ask for explicit confirmation**. Silently move to Round 2. + +## Round 2 — UI Design + +Now deliberate on how the feature presents to the user. Generate 1–3 candidate UI directions. Round 2 candidates may be full-layout wireframes when the whole surface is up for decision, or component-level candidates when only one area is ambiguous. Be explicit which level each candidate represents. + +Call **AskUserQuestion** with options that carry **markdown wireframe previews** in the `preview` field. Wireframes are ASCII boxes: + +``` +┌─────────────────────────────────┐ +│ Header · filter ▾ · +new │ +├─────────────────────────────────┤ +│ ▣ item ⋯ │ +│ ▢ item ⋯ │ +│ ▢ item ⋯ │ +└─────────────────────────────────┘ +``` + +Keep each preview readable in a chat-width column (~70 chars). Show real labels, real density, real affordances — not lorem ipsum. + +Cover the meaningful axes (layout type, hierarchy of actions, empty/loading/error states, dense vs roomy) in **at most 4 questions total**. Don't waste a question on a decision that has one obvious answer. + +When the user responds: +- Selected wireframes/options → **LockedUI**. +- If candidates were component-level, merge the selected components into **LockedUI** as integration decisions. The Final output still needs one composed/merged inline wireframe derived from **LockedFunctional**, **LockedUI**, and any selected **LockedExtras**. +- Free-text that implies new functional behavior (e.g. "add export button" implies an export *flow*) → see **Cascade rule**. +- Pure UI refinements stay in Round 2. + +Silently proceed to Round 3. + +## Round 3 — Extras, Quirks, Out-of-the-Box + +Now generate 4–8 candidate ideas the user *didn't* ask for but might love: micro-interactions, keyboard shortcuts, empty-state delight, power-user features, animations, accessibility wins, surprise affordances, anti-aesthetics-of-AI touches (favor warmth and specificity over generic monochrome polish). + +Call **AskUserQuestion** with `multiSelect: true` for the menu of extras. Each option's `description` should be one sentence explaining the idea and one sentence on the cost/benefit. + +Selected items → **LockedExtras**. + +If the user adds a new functional idea in free-text → **Cascade rule**. + +## Cascade rule (the core contract) + +A **new functional requirement** is any input — from the user OR self-detected from your own proposals — that adds, removes, or changes scope of the feature. Not a UI refinement. Not an extras pick. *New behavior the system must perform.* + +When detected: + +1. **Snapshot** the current round. +2. **Queue** the new piece in **PendingNewPieces** with a one-line description. +3. **Announce briefly**: "New functional piece detected: . Cascading it through R1→R2→R3 before resuming Round ." +4. **Run a focused mini-cascade for that piece only**: + - **R1 for new piece**: AskUserQuestion scoped to just the new piece's functional ambiguities. + - **R2 for new piece**: AskUserQuestion with wireframes scoped to just how this new piece integrates into the already-locked UI (do NOT redesign locked layouts — show how the new piece slots in). + - **R3 for new piece**: AskUserQuestion with extras *for this piece only*. +5. **Merge** the results into the appropriate Locked* sets. +6. **Resume** from the snapshotted round. + +Multiple cascades may stack. Process them depth-first; never lose the snapshot. + +### Self-detecting new functional scope + +Before sending a UI option or an extra, ask yourself silently: *does this option require behavior that isn't already in LockedFunctional?* If yes, you have a choice: +- Drop the option (cleanest). +- Or, if it's genuinely a great idea, **fire the cascade yourself** before sending it. Don't sneak new functional scope into UI/extras questions. + +## Final output + +After Round 3 completes with no pending cascades: + +1. Print a tight consolidated plan in chat with three sections: + - **Functional** — bulleted LockedFunctional. + - **UI** — bulleted LockedUI with one inline wireframe of the final composed layout. + - **Extras** — bulleted LockedExtras. + Keep total under ~50 lines. No filler. +2. Call **ExitPlanMode** to formally request approval. + +### Tool reference: ExitPlanMode + +Use `ExitPlanMode(): void` immediately after the Final output. It has no parameters and returns no value. Calling it signals that **LockedFunctional**, **LockedUI**, and **LockedExtras** are complete, including the final composed wireframe, and asks the user to approve leaving plan mode. Do not call it before all pending cascades are processed. + +## Anti-patterns (do not do) + +- Asking the user to "describe what they want" in prose. Always frame as concrete options. +- Sending more than 4 questions in a single AskUserQuestion call (tool max). +- Slipping new functional behavior into UI or Extras rounds without cascading. +- Asking explicit "lock in? yes/no" questions between rounds — proceed silently unless interrupted. +- Re-litigating LockedFunctional during R2 or R3 unless a cascade explicitly opens that piece. +- Generic AI aesthetics in wireframes — no `font-mono` aesthetic apologies, no centered-everything, no purple gradients in mocked copy. Be specific and warm. +- Long preamble. The user invoked `/plan`; jump to Round 1. + +## Minimal example flow + +User: `/plan a markdown note app with tags` + +You: silent deliberation → AskUserQuestion (R1): +- Q1: "How should tags be created?" (inline `#tag` parsing / explicit tag field / both) +- Q2: "Search scope when filtering by tag?" (notes containing tag / notes tagged-only / both modes) +- Q3: "Multi-user or single-user?" + +User selects + adds "and they sync to iCloud" (new functional scope). + +You: "New piece detected: iCloud sync. Cascading." +→ R1-for-sync: conflict resolution? offline-first? +→ R2-for-sync: sync-status indicator placement? +→ R3-for-sync: optimistic UI? merge-conflict modal? +Merge → resume Round 1. + +… and so on through R2 and R3 of the main plan, then ExitPlanMode. diff --git a/.agents/skills/source-command-audit/SKILL.md b/.agents/skills/source-command-audit/SKILL.md new file mode 100644 index 000000000..10769bd1e --- /dev/null +++ b/.agents/skills/source-command-audit/SKILL.md @@ -0,0 +1,26 @@ +--- +name: "source-command-audit" +description: "Audit recent work — trace error paths, probe edge cases, fix any bugs or gaps found" +--- + +# source-command-audit + +Use this skill when the user asks to run the migrated source command `audit`. + +## Command Template + +Audit the work you just did in this session. Don't stop at "it compiles" or "it looks right" — actively go hunting for what's wrong. + +1. **Retrace the changes.** List every file you touched. For each, re-read the final state (not just the diff you remember) so you see what actually lives there now. + +2. **Trace every error path.** For each changed code path: what happens on empty / nil / malformed input? When an upstream caller passes something unexpected? When a dependency throws or times out? Walk the failure branches, not just the happy path. + +3. **Hunt edge cases.** Off-by-ones, empty collections, unicode, concurrency, first-run vs. repeat-run, reduce-motion / accessibility, different device/viewport sizes, streaming vs. terminal states, cancellation, partial failure. Pick the categories that actually apply to what you changed and work through them. + +4. **Check the surrounding contract.** Did the change break any callers, tests, types, styling, or invariants elsewhere? Grep for references to anything you removed or renamed and confirm. + +5. **Fix what you find.** For each real bug or gap, make the fix directly. For anything genuinely ambiguous, call it out rather than guessing. + +6. **Report.** End with a short list: what you checked, what you fixed, and anything you deliberately left alone (and why). + +Be honest — if the work was already solid, say so in one line. Don't manufacture busywork. diff --git a/apps/ade-cli/README.md b/apps/ade-cli/README.md index 663548b9e..cb6767601 100644 --- a/apps/ade-cli/README.md +++ b/apps/ade-cli/README.md @@ -264,6 +264,10 @@ ade --socket app-control launch --command "npm run dev" --text ade --socket browser open http://localhost:5173 --new-tab --text ade --socket macos-vm status --lane lane-id --text ade --socket macos-vm start --lane lane-id --create --no-display --text +ade --socket macos-vm storage --text +ade --socket macos-vm get-credentials --vm-name vm-name --text +ade --socket macos-vm display-session --lane lane-id --text +ade --socket macos-vm detach --lane lane-id --text ade --socket update status --text ade --socket update check --text ade --socket update install --text diff --git a/apps/ade-cli/src/cli.ts b/apps/ade-cli/src/cli.ts index 0024981f1..c8fab8d8a 100644 --- a/apps/ade-cli/src/cli.ts +++ b/apps/ade-cli/src/cli.ts @@ -37,6 +37,7 @@ import type { SyncProjectSwitchRequestPayload, SyncProjectSwitchResultPayload, } from "../../desktop/src/shared/types/sync"; +import { MACOS_VM_PHASES } from "../../desktop/src/shared/types/macosVm"; type JsonObject = Record; @@ -385,7 +386,8 @@ const TOP_LEVEL_HELP = `${ADE_BANNER} $ ade proof status | list | screenshot | record Manage proof and computer-use artifacts $ ade ios-sim devices | apps | launch | tap Control iOS Simulator apps, capture, and input $ ade app-control launch | snapshot | click Inspect and drive Electron apps - $ ade macos-vm status | start | guide Run lane-tied macOS VMs for agent work + $ ade macos-vm status | start | restart | wipe | install-runtime | set-credentials | get-credentials | storage | display-session | detach + Run ADE's singleton Apple silicon macOS VM $ ade browser open | tabs | screenshot Use ADE's built-in browser pane $ ade memory add | search | pin Use ADE memory $ ade usage snapshot | refresh | budget Read provider quota usage and edit automation guardrails @@ -1149,18 +1151,30 @@ const HELP_BY_COMMAND: Record = { "macos-vm": `${ADE_BANNER} macOS VM - macOS VM commands provision and control lane-tied Apple silicon macOS - guests through Lume. ADE mounts the lane worktree into the guest with a - shared directory so host and guest edits stay in sync. Use runtime socket - mode when the Work sidebar and agents should observe the same live VM state. + ADE manages a singleton Apple silicon macOS VM for agent work: one VM per + install, with at most one VM-backed lane attached at a time. Onboarding + advances through a fixed 10-phase model (status reports the current phase + number and label). Detaching a VM lane converts it back to a local lane. Discovery and lifecycle: - $ ade macos-vm status --text Show provider readiness and lane VMs - $ ade macos-vm status --lane --text Show one lane's VM + $ ade macos-vm status --text Show provider readiness, current phase, attached VM lane + $ ade macos-vm status --lane --text Show one lane's VM record $ ade macos-vm provision --lane Pull/create a VM for a lane $ ade macos-vm start --lane --create Start the VM, provisioning if missing + $ ade macos-vm restart --vm-name Restart the VM (stop, wait, start) + $ ade macos-vm restart --lane --force Restart and treat any in-flight ops as cancellable $ ade macos-vm stop --lane Stop the lane VM $ ade macos-vm delete --lane --force Delete the VM record and provider VM + $ ade macos-vm wipe --force Destroy the VM disk and IPSW cache (next create re-onboards) + $ ade macos-vm install-runtime --vm-name Install ade-runtime inside the guest over SSH + $ ade macos-vm set-credentials --vm-name --username ade + Save guest username + password to macOS Keychain + $ ade macos-vm set-credentials --vm-name --username ade --password-stdin + Pipe password via stdin instead of being prompted + $ ade macos-vm get-credentials --vm-name Show saved guest username / savedAt (never the password) + $ ade macos-vm storage --text Show IPSW cache + VM disk volumes and free-space estimates + $ ade macos-vm display-session --lane Issue a short-lived VNC websocket session for the lane VM + $ ade macos-vm detach --lane Detach the lane from the Mac VM (lane becomes local) $ ade macos-vm guide --lane --text Print agent VM guidance $ ade macos-vm focus --lane Select the VM GUI target or raise its viewer $ ade macos-vm screenshot --lane Capture the VM through VNC or its viewer @@ -1170,6 +1184,13 @@ const HELP_BY_COMMAND: Record = { $ ade macos-vm type --lane "hello" Type into the VM GUI target $ ade macos-vm actions --text List callable macos_vm actions + Phase model (status --text prints "phase N/10 \xb7