Skip to content

feat(flow): compile_agent runs on Windows, macOS, Linux, RDP and Citrix apps - #1283

Merged
abrichr merged 4 commits into
mainfrom
claude/jit-surface-app
Oct 19, 2026
Merged

abrichr merged 4 commits into
mainfrom
claude/jit-surface-app

Conversation

@abrichr

@abrichr abrichr commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

compile_agent took only WebApp(url=...). It now also takes SurfaceApp: a Windows, macOS, Linux, RDP or Citrix app, named the way a deployment file's backend: section names it.

What changed

  • jit.SurfaceApp(backend_section, record_reader=, reset=, open_backend=) and SurfaceApp.from_deployment("deploy.yaml").
  • Recording, canaries and runs open the backend through backends.factory.build_backend. The agent drives it through the same AuthoringSession and CaptureComputer the MCP recording tools use.
  • The canary and every run are bound to the origin <kind>://<target>, for example macos://TextEdit. Window titles stay out of it, because they can carry a patient name. A program admitted on one origin is refused on any other.
  • Named refusals:
    • a section missing its target (for example "backend.kind 'linux' needs backend.linux_app and backend.linux_window_title");
    • kind: web ("use WebApp(url=...)");
    • a Citrix canary or run without rdp_readiness_text, refused before any backend opens;
    • the playwright_mcp dialect on a non-browser app.
  • WebApp is unchanged except for an open() method. The web path runs as before.
  • docs/JIT.md: a "Desktop and remote apps" section. The "Web apps only" limit is replaced.

What it does not do yet

On RDP and Citrix, a pixel click carries no verified record identity. So the standard run gate refuses the canary with "Identity coverage: ... UNARMED". run --profile standard refuses an unqualified pixel bundle the same way. Arming pixel identity inside the JIT review is a follow-up.

Tests

  • tests/test_jit_surface_app.py (fast, every kind): validation, origin binding (a program admitted on one surface is refused on the web and on another surface), the Citrix readiness refusal, and factory wiring.
  • tests/test_jit_surface_pixels.py: records MockMed through the real RemoteDisplayBackend and CitrixWorkspaceBackend. tests/surface_kiosk shows Flow the page only as a client window's pixels. The test checks the draft is bound to the surface, the note is lifted to a parameter, and the save effect is mined. It then checks the canary is refused before any input reaches the window. 2 passed in 23 s.
  • Existing JIT tests: test_jit.py and test_jit_lifecycle.py pass (111 tests with the new file). tests/e2e/test_jit_cua_e2e.py (web, not run in CI): 4 passed locally.

Part of the backend parity work (#1282).

🤖 Generated with Claude Code

Review fixes (second commit)

  • An admission policy names a desktop or remote app with its kind (rdp://10.0.0.5). A bare host names a web app only, so a web entry no longer covers the RDP desktop on the same address.
  • Non-web runs keep execution_origin empty, because hosted validation and the egress evidence read it as a browser origin. Replayer.run takes the surface identity as execution_target_origin, and only the JIT admission match reads it. openadapt-flow run --config passes it too, so an admitted JIT desktop program now runs from the CLI.
  • The origin is lower case and has no credentials: a Windows agent_url with userinfo is refused.
  • New tests:
    • every kind reaches app.open() and the Replayer with the right arguments;
    • the real Replayer refuses a program admitted on another surface before any action;
    • the pixel backends are built through the factory.
  • Not changed: the window title stays in the bundle's backend_hints. Replay needs it to bind the same client window, and it lives only in workflow.json, encrypted when the bundle is sealed. Two Windows VMs behind the same tunnel port share an origin; docs/JIT.md says to use one cache directory per environment.

…ix apps

jit.SurfaceApp names the app the way a deployment file's backend section
does. Recording, canaries and runs open that backend through the same
AuthoringSession and CaptureComputer the MCP recording tools use. The
canary and every run are bound to <kind>://<target>; window titles stay out
of the origin. Missing targets, a Citrix run without a readiness marker and
the playwright_mcp dialect off the browser are refused with named reasons.

Tests record MockMed through the real RemoteDisplayBackend and
CitrixWorkspaceBackend (pixels only, via a page shown as a client window)
and check that the standard gate refuses the unqualified pixel canary
before any input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… execution_origin

Review fixes for SurfaceApp:
- An admission policy names a desktop or remote app with its kind
  (rdp://10.0.0.5). A bare host names a web app only, so a web entry never
  covers the RDP desktop on the same address, and vice versa.
- Non-web runs keep execution_origin None (hosted validation and the egress
  evidence read it as a browser origin). Replayer.run takes the surface
  identity as execution_target_origin for the JIT admission match only, and
  openadapt-flow run --config passes it, so an admitted JIT desktop program
  runs from the CLI too.
- The origin is lower case and built in surface_selection.surface_target_origin;
  a Windows agent_url with credentials is refused.
- Tests reach app.open and the Replayer for every kind, check the Replayer
  refuses a program admitted on another surface before any action, and build
  the pixel backends through the factory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…click

The fixture's Approve And Close button calls window.close() in its onclick.
Playwright can raise TargetClosedError after delivering that click, which
failed flow core unit shard 4 on #1283. The test still requires the popup to
have closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/flow/tests/test_multi_surface_selfclose.py
@abrichr
abrichr merged commit a546d75 into main Oct 19, 2026
39 checks passed
@abrichr
abrichr deleted the claude/jit-surface-app branch October 19, 2026 03:34
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