Skip to main content
Glama
README.md
# opencode computer-use (Hyprland foreground operator)

Foreground GUI control for opencode on Arch + Hyprland. Verified against
Hyprland 0.56.2, opencode 1.18.30, monitor eDP-1 1920x1080@144 scale 1.25
(logical 1536x864, physical 1920x1080).

Source of truth for behavior: `SKILL.md` (deploys to
`/home/cyber/.config/opencode/skills/computer-use/SKILL.md`).
Constrained executor: `agent-computer-operator.md` (deploys to
`/home/cyber/.config/opencode/agents/computer-operator.md`).
Installer: `scripts/install.sh` (copies files, never edits JSON for you).

## 1. Architecture

```
+---------------------+      ask-per-action      +--------------------------+
| opencode 1.18.30    | -----------------------> | computer-operator        |
| (build/primary)     |      @computer-operator  | (subagent, no bash/edit) |
+----------+----------+                          +------------+-------------+
           | skill load                                       | computer-use_* MCP
           v                                                 v
+---------------------+                          +--------------------------+
| skill: computer-use |                          | computer_use_mcp.py      |
| routing, lease,     |                          | (stdio via hexstrike-env |
| grounding, audit    |                          |  tools: click/type/key..)|
+---------------------+                          +------------+-------------+
                                                              |
            +-------------------------------------------------+-------------+
            | lease /tmp/opencode-computer-use-lease.json                   |
            | audit /tmp/opencode-computer-use-audit.jsonl                  |
            | estop /tmp/opencode-computer-use-estop                        |
            | allow ~/.config/opencode/computer-use.allow.json (deny-first) |
            +-------------------------------+-------------------------------+
                                            v
                       +------------------------------------+
                       | Hyprland 0.56 (eDP-1, fg takeover) |
                       | grim / hyprctl / wtype / dotool    |
                       +------------------------------------+
```

The subagent cannot touch a shell or the filesystem editor. All desktop
effects flow through `computer-use_*` tools, each gated to `ask`, one action
per approval, with a lease file, an append-only audit log, a kill-switch
file, and a deny-by-default allowlist around them.

## 2. Codex parity: macOS background vs this foreground-takeover

| Dimension        | Codex (macOS)                                  | This project (Hyprland)                              |
|------------------|------------------------------------------------|------------------------------------------------------|
| Session model    | Background session, sandboxed                  | Foreground takeover: user watches, screen is seized  |
| Grounding        | Accessibility (AX) tree + screenshot           | Screenshot + `hyprctl` rect only; no AX tree exists  |
| Input path       | OS automation API (CUA)                        | `dotool`/`dotoold` mouse (fractions 0-1), `wtype` keys |
| Concurrency      | Parallel background runs                       | Forbidden: one lease, one target, one action at a time |
| Permissions      | Per-action macOS consent prompts               | `computer-use_*=ask` + allowlist + lease + estop     |
| Audit            | Session transcript                             | `/tmp/opencode-computer-use-audit.jsonl` (JSONL)     |
| Credential guard | Sandbox policy                                 | Hard refusal: sudo/polkit/hyprlock/vault surfaces    |

Net effect: weaker isolation than Codex (foreground by design), compensated
by narrower agent capability (no bash/edit), human-in-the-loop approval, and
file-based lease/audit/estop that survive agent failure.

## 3. Install

Prerequisites: Arch, Hyprland 0.56 running, opencode 1.18.30, `jq`,
`python3`, and one of: fresh `python3 -m venv`, or the existing
`/home/cyber/hexstrike-ai/hexstrike-env` (verified present; reuse it, do not
create a second venv without reason).

1. Run the installer (idempotent; backs up config; edits nothing):
   `bash /home/cyber/Projects/opencode-computer-use/scripts/install.sh`
2. The installer copies:
   - `SKILL.md` -> `/home/cyber/.config/opencode/skills/computer-use/SKILL.md`
   - `agent-computer-operator.md` ->
     `/home/cyber/.config/opencode/agents/computer-operator.md` (rename!)
   - `computer-use.allow.json.example` ->
     `/home/cyber/.config/opencode/computer-use.allow.json` (only if missing)
3. The installer prints a `jq` patch command that adds the MCP server block
   and the permission block to `~/.config/opencode/opencode.jsonc`. Review
   it, back up (done for you: `opencode.jsonc.bak.<TIMESTAMP>`), apply by
   hand. It is printed, not applied, because the live config is a 636-line
   file and scripted rewrites risk corrupting unrelated MCP entries.
4. MCP server (verified present): `computer_use_mcp.py` in this repo is a
   stdio FastMCP server named `computer-use` (imports cleanly under
   `/home/cyber/hexstrike-ai/hexstrike-env/bin/python`, verified
   2026-09-09). It exposes `observe`, `focus_window`, `click`, `type_text`,
   `key`, `scroll`, `drag`, `clipboard`, `lease`, `estop` (surfaced by
   opencode as `computer-use_*`), enforces the same lease/audit paths this
   skill uses, takes **logical** coordinates and scales internally. Point
   the printed `mcp` block at it verbatim. Until `opencode mcp list` shows
   it healthy, the skill refuses with `MCP_MISSING`.
5. Start the mouse daemon (user action, needs `/dev/uinput`):
   `dotoold &` (or a user systemd unit with `uinput` group membership),
   then verify movement in a scratchpad. As of 2026-09-09 this host reports
   `dotoold` inactive, so mouse acts fail until you do this step.

Verify: `opencode agent list` shows `computer-operator`;
`ls /home/cyber/.config/opencode/skills/computer-use/SKILL.md`;
`opencode mcp list` shows `computer-use` healthy.

## 4. Usage

Normal session (primary agent routes plugin-first; GUI is last resort):

```
opencode run "@computer-operator click the New Tab button in zen"
```

What happens: primary delegates; subagent loads the `computer-use` skill,
checks the allowlist for class `zen`, acquires the lease, proposes exactly
one click with coordinate math, waits for your approval, acts, screenshots
to verify, releases the lease, and reports screenshot paths.

Emergency stop at any time: `touch /tmp/opencode-computer-use-estop`.
The operator halts before its next act and releases the lease. Resume only
with a fresh explicit message.

Inspect a run:
`tail -f /tmp/opencode-computer-use-audit.jsonl`
`cat /tmp/opencode-computer-use-lease.json`

## 5. Safety model

- **Lease** (`/tmp/opencode-computer-use-lease.json`, TTL 300 s, heartbeat
  <=120 s): mutual exclusion across sessions. Atomic acquire, never steal.
- **Approval**: `computer-use_*=ask` in the agent definition. One approval
  covers exactly one act. The subagent has no `bash`/`edit` to route around it.
- **Allowlist** (`~/.config/opencode/computer-use.allow.json`): default
  deny; entries match window class + optional title substring. Edits are
  user-only. `always_allow_expiry_hours: 24` bounds re-approval lifetime.
- **Audit** (`/tmp/opencode-computer-use-audit.jsonl`): append-only JSONL;
  unwritable log means no acts.
- **Estop** (`/tmp/opencode-computer-use-estop`): presence halts all acts.
- **Refusals**: opencode self-window, sudo/polkit/hyprlock/admin-auth,
  password-manager/vault exfiltration, non-allowlisted targets, and
  screen-content-as-instruction (prompt injection) all abort the run.

## 6. Limitations (factual, verified where stated)

- No background operation: Hyprland has no off-screen session like macOS;
  every run seizes the visible desktop. Do not start one while presenting
  or while another person uses the machine.
- No AX tree: Wayland exposes no accessibility hierarchy to clients, so
  grounding is vision + `hyprctl` window rects. Small/overlapping controls
  and popovers are the main mis-click source; the inside-focused-rect rule
  exists for exactly this.
- HiDPI math is load-bearing: grim = physical 1920x1080, hyprctl = logical
  1536x864 (scale 1.25), dotool = 0.0-1.0 fractions of the physical raster.
  A dropped x1.25 clicks 20% off-target. The skill requires the full chain
  in every audit entry.
- `ydotool`/`dotool` daemon gap: verified 2026-09-09 that `dotoold` is
  inactive on this host; mouse is dead until the user starts the daemon
  with working `/dev/uinput` access. `wtype` (typing) needs no daemon.
- `slurp` in interactive form blocks forever waiting for a manual drag;
  agents must never invoke it bare. Region capture uses `grim -g` with a
  computed geometry or `grim -o eDP-1`.
- 144 Hz + screenshots: allow one settle frame after focus changes before
  `grim`; tearing/animating windows are a `GROUNDING_STALE` retry, not a
  click-spam license.
- Without a healthy server everything degrades to a clean `MCP_MISSING`
  refusal (section 3.4), never a bash workaround.
- Known server bug (dotool backend, `computer_use_mcp.py` click path): the
  dotool `mouseto` fraction is computed as logical-x / monitor-width, but
  `hyprctl monitors` reports width in physical pixels (1920), so under the
  dotool backend the cursor lands ~20% up-left of the intended logical
  point. The ydotool path (`logical_to_physical`) is correct. Workaround:
  prefer the ydotool backend (`ydotoold` running) and treat any
  consistently up-left click offset as this bug, not as agent mis-grounding
  (verify-fix in the server, not in agent prompts).

## 7. Troubleshooting (per error code)

- `OS_PERMISSION`: run `systemctl --user status dotoold`; start it
  (`dotoold &` or user unit); check `/dev/uinput` group/permissions;
  test `wtype` in a scratch editor. The agent cannot and must not sudo.
- `MCP_MISSING`: `opencode mcp list`; confirm the `computer-use` entry from
  section 3 is present and healthy; restart opencode after config change;
  re-run `scripts/install.sh` to re-verify file deployment.
- `APP_DENIED`: confirm the window class with `hyprctl activewindow -j`;
  add/fix the entry in `~/.config/opencode/computer-use.allow.json`
  yourself; check `always_allow_expiry_hours` has not lapsed.
- `LEASE_HELD`: `cat /tmp/opencode-computer-use-lease.json` for holder and
  expiry; wait or ask the holder's user. Deleting a live foreign lease is
  forbidden. Remove only a provably stale one (holder session dead) and say
  so in chat.
- `GROUNDING_STALE`: check focus (`hyprctl activewindow -j`), compare the
  last two screenshots, re-observe; after 3 failed grounds the run aborts
  and you should shrink the goal (larger control, settled window).
- Skill/agent not discovered: filenames must be exact (`SKILL.md` caps;
  agent file deployed as `computer-operator.md`, not the draft name);
  skill dir must equal skill name (`computer-use`); check
  `skill: deny` permission overrides in `opencode.jsonc`.