Skip to content

feat(gateway): image generation offload - job-service routing, sync_id, poll progress, per-device auth - #86

Open
siddWednesday wants to merge 202 commits into
mainfrom
feat/image-gen-offload
Open

feat(gateway): image generation offload - job-service routing, sync_id, poll progress, per-device auth#86
siddWednesday wants to merge 202 commits into
mainfrom
feat/image-gen-offload

Conversation

@siddWednesday

@siddWednesday siddWednesday commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What this does

Review note - merge order: this branch sits on top of feat/windows-parity (it uses its per-device token infrastructure). Merge that to main first; this PR's diff then collapses to the single offload commit (617e982). Mobile half: off-grid-ai/OGAM#638.

The desktop half of phone->Mac image-generation offload: a paired (or any LAN) phone POSTs the gateway's existing /v1/images/generations in async mode and receives a desktop-quality image; the mobile half is the matching feat/image-gen-offload branch in OGAM.

  • Image routes run through ImageGenerationJobService instead of calling generateImage() directly. That restores the single-job admission for gateway callers (second request = 429), and gives every gateway image a syncId + sidecar - so it appears in this Mac's own gallery and ships to paired devices over the existing mesh share, exactly like a locally started generation.
  • Responses carry sync_id so a paired requester dedupes the mesh copy against the HTTP result (the phone uses it as the local file id).
  • Pending polls report live progress ({stage, step, total}) - the phone maps this into its normal generation card.
  • Opportunistic bearer auth on the image routes: a presented token is verified against the live per-device action tokens from this branch's tool-grant mesh (401 on mismatch); no credentials keeps the gateway's documented open-LAN posture unchanged.

Verification

Unit + integration: 5 pure tests (image-route.test.ts: response shaping, sync_id, poll progress gating) + 4 against the real booted gateway (image-route-auth.integration.test.ts: open posture reaches 501, valid token accepted, invalid = 401 on both routes). Full suite green through the pre-push gate.

Verified live on a dev build (2026-08-26): 501 honest reason with no model; SDXL pulled headlessly via /v1/models/pull; sync generation returned b64 + sync_id with the sidecar landing in generated-images/; async 202 + poll showed step 3/4 -> 4/4 -> completed with the result held at the poll URL; concurrent request 429'd; bad bearer 401'd. End-to-end phone->Mac generation confirmed on a real iPhone against this branch (image generated on the Mac, delivered into the phone chat).

No screenshots: the change is headless (HTTP surface); the live curl transcript above is the visual. The user-facing surface ships in the OGAM PR.

🤖 Generated with Claude Code

siddWednesday and others added 30 commits August 11, 2026 16:26
Research + decided direction: intents and MCP as primary action paths,
vision-based agent loop as fallback, engine as @offgrid/use in shared,
vision model as a downloadable catalog entry (GUI-Owl-1.5 / Qwen3-VL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five phases, Aug 12 - Dec 2 2026, one demoable checkpoint per phase.
Phase 1 (semantic rail) ships standalone value by Sep 9; the vision
model install is only needed from phase 3 week 3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
16 weeks to 9 (Aug 12 - Oct 14). Code-heavy phases shrink the most
(engine 4wk to 2wk); integration-heavy phases keep slack since review,
TCC flows, and real-app iteration do not compress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sibling shared checkout + file: consumption decision added to phase 0;
mobile follows desktop as an adapter-only project on the same engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bile adapter

Approach doc's open questions become a decision log (graph roles, eval
harness, sequencing decided; model pick narrowed to install time;
package name pending lead confirm). Plan gets an After v1 section with
the mobile adapter's prerequisites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Timeline table now shows which repo each phase builds in; dependencies
record the OGAD push-access ask (pull-only as of Aug 11, shared has push).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… re-phased timeline

From the Clawbot/product-UX research: embedded agent browser
(WebContentsView + webContents.debugger CDP, indexed snapshot, per-site
cards, takeover with capture-kill) becomes phase 3, ahead of native GUI
- zero OS permissions, no new models, shippable cut at Sep 23. Native
adapter + vision moves to phase 4, hard targets to phase 5 (ends Oct
27). Direct-reuse list added (UI-TARS sdk/ScreenMarker, nanobrowser,
nut-js fork, macos-automator-mcp, bytebot takeover pattern, Peekaboo).
Brand guidelines (off-grid-ai/brand + @offgrid/design tokens +
docs/DESIGN.md) bound as standing build rules. OpenClaw teardown
informs the zero-setup bar and the safety avoid-list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 of computer use. Widens the MCP-only `mcp:proposeApproval` hook
into a shared `actions:proposeApproval` seam every executor (MCP today,
computer + browser next) can gate through. Adds an ActionRisk taxonomy
(read/navigate/mutate/irreversible) with a single shouldGate() source of
truth, and per-executor riskOf() classification - a GUI click's risk is
not derivable from a tool name the way the old isActionTool regex assumed.

Backward compatible: proposeActionApproval prefers the new hook but falls
back to the legacy name via hasHook(), so a desktop-pro build that has not
yet migrated keeps gating MCP writes instead of silently running them.
MCP behaviour is unchanged (read-verb tools -> read, everything else ->
mutate), pinned by the existing queue-vs-execute dbtest plus new risk tests.

Verified: tsc (node + web), 41 unit tests, 7 dbtest, eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 0 packaging groundwork. Adds the Info.plist usage-description keys
the semantic action rail needs — Apple Events (Messages/Mail/Notes),
Calendars (+ pre-14 legacy key), Reminders, Contacts, Photos — so a
hardened-runtime build is granted each capability instead of being
refused before the OS prompt. The apple-events entitlement was already
present. Copy follows off-grid-ai/brand: outcome first, privacy as proof,
no em dashes. A source-reading test pins every key + the entitlement and
enforces the no-em-dash rule on the strings added here.

Verified: 8 new tests, YAML parses, config-reading tests green, eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 semantic rail, first slice. Adds the single seam every native
capability goes through: a Swift one-shot helper (scripts/actions-helper)
that takes one JSON command, performs a scoped EventKit action, and prints
one JSON line; and a Node invoker (runNativeAction) that resolves the
binary packaged-vs-dev like ocr.ts and parses the reply. Handled failures
(denied permission, bad args) are in-band { ok: false } results, never
throws, so the tool loop has one shape to report.

Backend covers calendar create + list to start; the switch and command
namespacing leave reminders/contacts/photos as additive cases. Pure logic
(contract, path resolution, response parsing) is split into
native-helper-logic.ts and unit tested; the Swift compiles clean under
swiftc 6.2 targeting macos13. Not yet wired to a tool or shipped in CI -
that lands with the tool that calls it, so no dead binary ships early.

Verified: tsc node, 11 logic tests, swiftc build, eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 semantic rail, made reachable. Registers a native-action tool
extension (macOS-only) exposing calendar_create_event and
calendar_list_events to the model; create is a mutate that offers itself
to the shared approval seam (queues in pro, runs in the free build),
list is a read that runs directly. Both route through runNativeAction to
the EventKit helper. Adds 'native' to ActionKind so the approval UI and
audit can label semantic OS actions apart from GUI computer use. The
release workflow now builds + stages the helper into resources/bin, so a
packaged build ships it (self-contained; if it fails the tools report
'not available' and nothing else breaks).

Boundary injection mirrors the MCP extension, so the gate-then-run
contract is unit tested end to end without a real EventKit call.

Verified: tsc node, 90 tests across actions+tools+hooks, release.yml
parses, eslint clean on new files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reminders_create (mutate, gated) and reminders_list (read) alongside the
calendar tools, via EventKit reminder access in the helper. Extracts a
shared formatCreated() so each create tool reuses one confirmation shape
instead of re-encoding it.

Verified: swiftc build, tsc node, 16 tool tests, eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
contacts_search (read) via the Contacts framework (CNContactStore),
returning matching names with phones and emails - the lookup the message
and event tools need to resolve a person. Read-only, so it runs without
approval.

Verified: swiftc build, tsc node, 82 tests across actions+tools, eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
messages_send (iMessage) and mail_send (Mail), both mutates gated through
the approval seam - the 'message Ali' half of the rail that composes with
contacts_search to resolve a name to a handle. Backed by AppleScript via
NSAppleScript in the helper; user-supplied values are escaped before
interpolation so a quote or backslash cannot break the script or inject
statements. Uses the apple-events entitlement + NSAppleEventsUsageDescription
already staged in phase 0.

Verified: swiftc build, tsc node, 19 tool tests (incl. a messages_send
gating case), eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
open_url opens a web page, mailto: draft, or app scheme (whatsapp://send)
via NSWorkspace - the navigate that starts the WhatsApp path and the first
use of the non-gating navigate risk class (it opens, it does not submit,
so no approval).

Verified: swiftc build, tsc node, 85 tests across actions+tools, eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build-mac-local.sh now builds and copies actions-helper into resources/bin
alongside the other native helpers, mirroring the release.yml step, so a
local packaged build includes the semantic-rail backend (otherwise the
calendar/messages/mail tools report 'not available').

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parseDate now tries full ISO 8601 (with timezone) first, then falls back
to the local forms a model commonly emits - 2026-08-13T15:00:00,
2026-08-13T15:00, and 2026-08-13 - interpreted in the user's timezone.
Without this a create-event/reminder call from the model would fail
whenever it omitted the timezone.

Verified: swiftc build; invalid dates still rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	electron-builder.yml
#	src/main/bootstrap/hookRegistry.ts
#	src/main/index.ts
Supersedes the mobile-use-replication framing. Leads with the product:
a proactive, context-grounded local assistant. Two generators (routine:
detected + demonstrated; reasoned: commitment + world-knowledge +
gap-check) feed one gated spine (resolve slots from memory -> gate shows
resolved values -> execute via rails cheapest-first). Rail hierarchy with
vision correctly placed last; three-kinds-of-seeing distinction; the
demonstration recorder; memory-grounded slot resolution; honest
reliability tiers; the moat (knows you + proactive + private + routes to
the cheapest reliable rail). Plan re-phased: reasoning engine first
(the magic, memory-driven, safe), vision fallback last. Records rail 1
as already built.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phases 2-6 now run Aug 13-26 (phases 0-1 already done). Reasoning engine
Aug 13-16, engine Aug 17-18, recorder+replay Aug 19-22, agent browser
Aug 23-24, vision fallback + hard targets Aug 25-26.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Self-contained brief for generating the demo artifacts: the product
vision, the inlined brand system (Menlo, emerald-on-black, brutalist,
dense), a five-screen day-in-the-life story (assistant home, resolved
approval card, flight nudge, record-a-routine, routines library), copy
voice, deliverable format, and the anti-patterns to avoid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Come-ups live in Day (Needs you section, ephemeral rows - never tabs);
the gate is inline + the existing Actions queue; Routines is the one new
tab; record is a modal from it; away-presence via toast + menu-bar count.
Adds the general-engine point (no per-situation verticals - no Flights
tab) and Screen 1 now shows a mix of situations (flight + deck + renewal
+ morning brief) so the generality reads at a glance. Uses the real
left-rail nav names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surveys who is already doing each piece of the proactive assistant:
proactive surfacing (Magic Cue, Pulse, Recall), context resolution
(Glean, Shortwave, Copilot), commitment detection (Gmail Nudges,
notetakers, MSR speech-act research), routines/teach-by-demo (Automator,
Shortcuts, RPA recorders + self-healing), and confirm-before-acting
(Operator, Manus, the false-confirmation failure). Three strategic
findings: local-first is open whitespace (Rewind/Limitless->Meta, Dot
shutdown), context-resolution-with-evidence-before-acting is unshipped
(our differentiator), and the GUI-automation reliability ceiling is real
(Mariner/ChatGPT-travel killed) - validating cheapest-rail-first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Folds in the two parent researchers' consolidated syntheses. Routines
section gains the academic build blueprint for slot induction + self-
healing (Agent Workflow Memory, Alloy, SUGILITE/APPINITE, LUMOS AX-thesis,
hierarchical-not-flat, action-effect verification, Morae consequential-
step gating, tiered permissions, graduated trust, trycua background AX
control). Adds two lessons: detect-completion-and-auto-retire commitments
(only a local memory product can, our biggest anti-nag move) and bind the
approval card to the exact executing payload (the Alexa+ read-back bug).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ence/confidence card

Rewrites the brief from the actual app screenshots so generated artifacts
look like Off Grid AI: the real left-rail nav (with Routines added after
Actions) and its emerald active state, the real component vocabulary
(outlined buttons, pill toggles, status tags, the bottom-CTA-card shape
reused for come-ups/toasts), Menlo everywhere, flat + outlined + ~6px
radius + dotted-grid, exact dark/light tokens. Corrects the earlier
over-literal 'razor-sharp brutalist' framing that made output not look
like the app. Screen 2 now shows each resolved slot with its evidence
and a confidence tag, plus a low-confidence 'which deck did you mean'
disambiguation variant - the screen no competitor ships.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pen questions

The system design from the architecture discussion: the model proposes /
durable queue guarantees split, the Action durable record + state machine,
the reliability stack that survives a weak local model, the typed
action-handler registry + scope map (two reliability tiers), and the one
cross-platform core + adapters (mobile included). Section 7 locks the
answered decisions (incl. scope and the model-agnostic pipeline); section
8 explains the six open questions for the team - exactly-once per rail,
scheduling, trust graduation, mobile v1 target, open-core placement,
verification depth - each with options, the decision, and our lean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two layers: the assistant (brain) and the rails (actuation, behind the
DeviceController interface). Computer use = the vision rail specifically,
not the whole layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… adapters)

An at-a-glance component diagram: generators -> the assistant (queue,
resolver, gate, router, verify) -> DeviceController port -> the rails
(semantic, browser, accessibility, vision) -> per-platform impls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In the Wednesday TRD + PRD house style: System Architecture as a C4 model
(Context L1 + Container L2), a sequence/swimlane by actor for the
send-the-deck flow, and the product user flows (Day + Chat entry -> gate
-> verify, plus the two ways a routine is born). Mermaid so they render on
GitHub / mermaid.live / an artifact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
siddWednesday and others added 24 commits August 21, 2026 15:52
…pro too

The inline approval card (ActionGateDock, mounted in the chat) was only fed when
no pro queue was listening - so in a pro build the gate always went to the pro
queue (a notification) and the in-chat Approve/Reject card never appeared. That
was flagged in the code as "pro queue wins until the migration".

Do the migration: gateHost now parks AND emits the inline card whenever a human
is needed (pro queue accepted it, or an inline surface is registered). The
notification and the in-chat card are two views of the ONE engine gate -
whichever the user acts on calls resolveActionGate for the same actionId
(idempotent), and the other view settles on the outcome broadcast. A
chat-initiated computer-use task is now approvable right where it was asked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add registerApprovalModeProvider / computerApprovalMode to gate-host so the user's
"Auto-approve vs Ask every time" choice governs the gate. gateHost short-circuits
to approve when the mode is 'auto' (the task still journals and its outcome shows
in chat); 'ask' (the default when no provider is registered) parks for approval as
before. Pro owns the persisted setting + the Sync-sharing toggle and registers the
provider - core just exposes the seam, so the free build has no computer-use logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d + mesh)

The inline gate surface was a single slot, so only one consumer could render a
parked gate. Mobile approvals need a second: pro forwarding the gate to paired
phones over the mesh, alongside the desktop chat card. Make it a Set of
subscribers - registerInlineGateSurface adds/removes, gateHost fans each parked
gate to all of them. Each surface resolves the ONE engine gate via
resolveActionGate; the first verdict wins, the rest are no-ops.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Explore surface now renders each capability as a bordered panel (icon
tile + teaches line) holding a dense card grid, adapting to its container
(two panel columns on the Explore screen, one in the chat empty state).
Preset titles become short capability labels - the prompt-phrased titles
("Find me a flight to book", "What did I work on today?") no longer leak
the seeded prompt onto the card. Robust ungated presets get a ready
marker; the Explore screen gains a real page header with catalog-computed
meta and hides the section's compact intro via showIntro.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boots the free build on a fresh profile, walks the Explore screen and the
chat empty state, and asserts the capability panels render with card
labels while the seeded prompt never appears on the surface. Screenshots
captured for evidence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every preset now carries its own Phosphor icon in the catalog SSOT
(AirplaneTilt, SpotifyLogo, Crop, ...) so both placements show the same
mark; the card leads with the icon and warms it emerald on hover. Each
panel header gains a small runs-count meta. Tests cover the icon
invariant, the per-card rendering, and the panel counts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…app quits

The agent WebContentsView runs with backgroundThrottling off (so the agent can
work while the user does other things), and it was only ever hidden - bounds set
to zero - never muted or torn down. So a web_task that started a video kept its
audio playing after the pane closed, the window was hidden, or the app was
"quit" (on macOS a window close leaves the process alive).

- Mute the view's audio whenever it's hidden, unmute when shown, so closing the
  browser pane actually silences it.
- React to the window too (setRegion only fires while the pane is mounted): mute
  on window hide, fully dispose (remove + close the WebContents) on window close.
- disposeBrowserHost() on before-quit stops media immediately rather than
  whenever the process finally exits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the amber "this model can't ground" warning (VisionGrounderNudge) - it added
noise on every non-grounder computer-use run. Removed the render + its import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The card sat on screen while the action ran and the outcome made its way back.
resolve() now drops the pending card immediately on approve/reject/edit; the
outcome still lands as its own row, and an edit re-gates as a fresh pending event.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ip notch

The left-edge resize handle was covered by the native WebContentsView below the
header, so it was only grabbable in a thin strip. Inset the web region (ml-4) to
give the handle a real full-height gutter, and swap the faint bar for a centered
DotsSixVertical grip so it clearly reads as draggable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The live step narration for a web task sat in a feed under the browser pane.
Move it into the chat turn: MemoryChat subscribes to browser:onStep + onTaskState
and renders the recent steps in the streaming assistant turn (a new task resets
them, and they fall away when the turn ends). WatchedBrowserPane drops its step
feed + summary and keeps just the goal, live browser view, and takeover prompt.
Computer-use task steps are unchanged (they stay in their own small modal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The card labels/blurbs are unchanged; the actual prompt each preset SENDS is now
defined - it names the site or app and states the task plainly, so it routes
cleanly and reads sensibly, e.g. find-flight now sends "Go to skyscanner.com and
help me find a flight to book. I will give you the route, dates, and budget when
you ask." Named targets: Skyscanner, Google Maps, Google Shopping, Spotify,
Preview, Mail; the memory/phone prompts point at what was captured on the Mac.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolutions:
- packages/models: accept main's de-vendoring (now file:../shared/packages/models,
  where the grounder work already landed).
- pro: adopt main's submodule model (.gitmodules); pointer bumped to the synced
  desktop-pro feature commit in a follow-up once desktop-pro is synced.
- MemoryChat.tsx: took main's rewrite, re-grafted the 12 chat-approvals surgical
  changes (ExploreSection seed handoff, ActionGateDock/VisionSupervisorOverlay,
  self-contained WebTaskStepFeed for the streaming turn); dropped the MessageNudge
  cutoff change (superseded by main's ResponseCutoffNotice).
- ipc.ts: main's superset chat-stream-state imports.
- tools.ts: widen the planning-path return to main's plural imageRequests contract.
- vitest.db.config.ts + release.yml: union of both sides' exclusions / build steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… by the sync

- pro: bump submodule pointer to the synced desktop-pro feat/mesh-tool-grant (4b882d8).
- docs: restore the directory name (an earlier commit accidentally renamed docs/ ->
  docshi/); main carries docs/, and the coverage ledger + release checklist read docs/.
- WatchedBrowserPane: guard the browser IPC method calls (onTaskState?./onTakeover?.)
  so App integration tests (and a partial preload) don't crash on mount when the
  optional browser API isn't wired.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vision rail's grounder returns click points in the captured screenshot's DIP
space. macOS positions the cursor in points, so those went through raw and worked;
a DPI-aware Windows process positions in physical pixels, so a click on a 125/150%
display landed short. New pure coordinate-mapping module maps a DIP point ->
actuation space (x scaleFactor on win32, x1 on mac) + the display origin offset for
multi-monitor, wired into vision-host's actuate(). 12 unit tests cover mac Retina
(no scale), Windows 100/125/150/200%, second-monitor offset, and per-verb mapping.

Mixed-DPI multi-monitor is a documented follow-up (no physical-bounds API).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nut-tree-fork/nut-js drives the vision rail's cursor/keyboard via the prebuilt
(N-API, ABI-stable) libnut-win32 addon - an optional dependency that npm can skip.
When it is missing, loadActuation() returns null and the rail refuses every task
with no crash and no obvious cause. Gate the Windows build on the prebuilt .node
being present after npm ci, mirroring the fetch script's fail-on-missing-exe rule.
Packaging + Electron-ABI handling already work via postinstall install-app-deps +
electron-builder smartUnpack; this closes the silent-omission hole.
The browser/semantic/vision rails live in core and are per-platform via one
pickByPlatform seam. Browser (CDP) and semantic (local Outlook COM: create +
read-back + undo) are Windows-ready and tested; the vision computer-use rail is
code-ready (nut.js actuation + DPI/scale coordinate mapping + CI presence gate)
and needs a real-Windows actuation run. Graph online fallback + mixed-DPI
multi-monitor are the noted follow-ups. permissions.ts already no-ops off darwin.
The Windows build failed at typecheck for two reasons, both in the shared setup:
 - @offgrid/models was never built (only sync + use were), so every 'file:'
   consumer hit 'Cannot find module @offgrid/models'. Add its build step
   (mirrors release.yml).
 - the shared checkout matches by BRANCH NAME (github.ref_name). This core branch
   depends on shared feat/use-approval-tiers (effectId/undo/computer_task in
   @offgrid/use) whose name does not match, so it fell back to shared main and the
   types were missing. Add a shared_ref dispatch input (mirrors pro_ref) so a build
   can pin the shared ref explicitly.

To build this branch: dispatch with shared_ref=feat/use-approval-tiers and
pro_ref=feat/mesh-tool-grant.
…ownloaded

Computer use swapped in the hard-coded grounder (UI-TARS) before running, and
setActiveModel doesn't check the files are present - so with UI-TARS not
downloaded, the llama-server load failed ('Models not found') and the whole task
died silently ('nothing happens after Approve'). Now the swap is gated on the
grounder actually being installed: if it is, swap as before; if not, run computer
use on the ACTIVE model and warn (vision-host already flags a non-grounder), never
hard-failing on a missing model. Pure decision extracted to grounder-plan.ts (3
tests); grounder-loader wires the real installed-check + does the fallback.

Note: a generic vision model is not a trained GUI grounder, so clicks are less
precise than UI-TARS - but the rail now runs end-to-end (capture -> model ->
actuate) on any vision model, which is what unblocks Windows testing.
Computer use on Windows had NO tier-1 accessibility rail - resolveNativeApp/
routingSnapshot were hard-gated to darwin, so every task fell to vision (with no
app-open step and, without a grounder, floundering). Bring the mac AX rail's
'open the app, drive it by element LABEL with a normal chat model' path to Windows:

- ax-uia-script.ts: pure PowerShell + UI Automation script builders (apps list /
  element tree / foreground), emitting the EXACT parseAxElements contract
  ([WINDOW_TITLE] + per-element JSON). UIA BoundingRectangle is physical px, which
  is nut.js's space on Windows, so no DIP scaling here. 9 tests (contract +
  injection safety). Mirrors the Windows semantic rail's PowerShell approach - no
  compiled binary to build or ship.
- ax-win.ts: the Windows AxBackend - runs those scripts via powershell.exe,
  fail-closed to []/null so a dead UIA read falls through to vision.
- ax-host.ts: refactored to a platform backend seam (macAxBackend = the Swift
  helper + open -a, unchanged; windowsAxBackend for win32). resolveNativeApp /
  routingSnapshot / runTask now go through backend.available()/listApps/activate/
  snapshot instead of darwin-hardcoding. Mac path byte-for-byte the same.

computer_task is already exposed + hinted on win32, so this activates tier-1
automatically. Needs on-Windows validation (UIA element quality, coords, app
resolution, perf) - the PowerShell behavior can't be exercised from macOS.
Carries desktop-pro's 'a remote eviction must not delete the Pro license' fix, so a
build from this branch lets you evict a phone and re-pair without the desktop going
dark or losing its license.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Core's /mcp action gate no longer trusts a single global token. It delegates to a
live-token provider that the pro sync layer registers (registerActiveActionTokens),
so ONLY the tokens of currently paired + tools-allowed devices authorize; the
legacy global token stays as the no-pro fallback. authorizeBearerAny matches a
bearer against the live set - an empty set (no paired peer / un-paired) fails
closed, which is what makes tool access actually revoke on un-pair.

Bumps pro to d3bad1a (per-device token on the pairing row).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… Release)

Dispatch with artifact_only=true to get the SAME signed + notarized macOS DMG
uploaded as a repo-scoped, auto-expiring Actions artifact instead of a published
Release. Gates off the release create/upload, the pro-mac.yml + latest/nightly
aliases, Slack, and the whole Windows job - nothing lands on the releases page or
the update feed. Normal releases are unchanged (default false).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…c bearer auth

The three image POST routes now start jobs via ImageGenerationJobService
instead of calling generateImage directly - restoring the single-job
admission (busy = 429), giving gateway images a syncId + sidecar so they
land in this Mac's gallery and ship to paired devices over the mesh like
local generations. Responses carry sync_id so a paired requester can
dedupe the synced copy; pending polls report live stage/step. A presented
bearer is verified against the live per-device action tokens (401 on
mismatch); no credentials keeps the documented open-LAN posture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b3055a7-ee71-4ba1-8c71-830e3cd7b0e9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@siddWednesday
siddWednesday changed the base branch from feat/windows-parity to main August 26, 2026 11:22
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant