← Back to skills

Domain skill

manus

Markdown synced from browser-harness domain skills.

Agent prompt

Use this skill

Copy this prompt into your coding agent to make it enable browser-harness domain skills and read this exact domain folder before automating.

Set up https://github.com/browser-use/browser-harness for me if it is not already installed. If setup is needed, read `install.md` first to install and connect it to my real browser. Then read `SKILL.md` for normal usage and always read `helpers.py` because that is where the browser-harness functions are.

Enable domain skills if they are not already enabled by setting `BH_DOMAIN_SKILLS=1` for browser-harness. Use the `manus` domain skill from `agent-workspace/domain-skills/manus/`. Read every markdown file for this domain before inventing an approach:
- agent-workspace/domain-skills/manus/tasks.md

Use those domain-skill notes to complete my task for `manus` in my real browser. When you open a setup, verification, or task tab, activate it so I can see the active browser tab.

Skill contents

What the agent will read

Submitting Tasks, Awaiting Completion, Sharing Results

tasks.md

Source
  • Manus (manus.im) is a general-purpose agent that runs long-form tasks in a cloud sandbox. The web UI is a Next.js SPA; the runtime API is a separate host (api.manus.im) speaking Connect RPC. You must be signed in...
  • App home / new-task composer: https://manus.im/app
  • Running or completed task: https://manus.im/app/<taskId> — <taskId> is a 22-char base62-ish opaque id (character set includes letters, digits, and -/). The same id is the session id on the API side.
  • Public share page: https://manus.im/share/<taskId> — only reachable after a ShareSession call (clicking Share).
Show full markdown

Manus (manus.im) is a general-purpose agent that runs long-form tasks in a cloud sandbox. The web UI is a Next.js SPA; the runtime API is a separate host (api.manus.im) speaking Connect RPC. You must be signed in before the harness can drive it — hit the auth wall and stop, don't type credentials.

URL patterns

  • App home / new-task composer: https://manus.im/app
  • Running or completed task: https://manus.im/app/<taskId><taskId> is a 22-char base62-ish opaque id (character set includes letters, digits, and -/_). The same id is the session id on the API side.
  • Public share page: https://manus.im/share/<taskId> — only reachable after a ShareSession call (clicking Share).
  • Library / past tasks: listed in the left sidebar; the list is paged via session.v1.SessionService/ListSessions.

The title of a task page is auto-summarized from the prompt (it shortens, reflows, and sometimes paraphrases). Do not match on the original prompt text when identifying a task in the sidebar — use the taskId in the URL as the durable handle.

Path 1: Private API — api.manus.im Connect RPC

All API calls go to https://api.manus.im/ with the Connect RPC shape /<package>.v<N>.<Service>/<Method>. Requests are POST with content-type: application/json; the Connect JS client encodes request JSON as a Uint8Array before calling fetch, so a naive fetch-hook may log bodies as <<bytes:N>> — the wire content is still JSON matching the content-type (both directions). Auth is a session cookie set by manus.im, so in-browser calls Just Work if you're logged in — fetch("/session.v1.SessionService/...") from the Manus origin is fine, but cross-origin from a scraper is not.

Services observed on the wire:

ServiceKnown methodsPurpose
session.v1.SessionServiceListSessions, GetSession, ShareSession, UpdateReadPositionTasks (called "sessions" on the API). GetSession is the poll target for status. ShareSession flips public access and returns the share url.
orchestrator.v1.OrchestratorServiceGetSessionRuntime/plan state for an in-progress task.
cloud_pc.v1.CloudPCServiceListAttached Cloud PC instances (sandbox VMs).
desktop.v1.DesktopServiceGetDesktopDevicesEnumerates desktop agent connections.
user.v1.UserServiceWebdevUsageInfo, SetUserClientConfig, GetHelpCenterToken, PickEmailUsersUser settings + quota.
user.v1.UserPublicServiceGetGlobalSettingsPublic feature flags.
team.v1.TeamServiceListTeamTeam membership.

To poll a task to completion without scraping the DOM:

python
# Browser fetch from inside the page — auth cookies are attached automatically
status = js(r"""
(async()=>{
  const r = await fetch('https://api.manus.im/session.v1.SessionService/GetSession', {
    method: 'POST',
    headers: {'content-type': 'application/json'},
    body: JSON.stringify({sessionId: location.pathname.split('/').pop()})
  });
  return {status: r.status, body: await r.text()};
})()
""")

(Exact field names in the request payload are not documented publicly — inspect the outgoing request with the fetch-hook snippet below the first time you run against a new Manus build.)

There is no plain REST/JSON convenience layer — every method is Connect. http_get against API routes will not work; you have to either drive the browser or speak Connect yourself. If you need a pure-HTTP client, open a PR with the Connect wire format once you reverse-engineer the request shapes.

Path 2: Browser DOM submission

bash
browser-harness <<'PY'
new_tab("https://manus.im/app")
wait_for_load()
wait(1.5)  # SPA still hydrating; composer is a TipTap editor that mounts late
# Locate the editor and click into it — the ProseMirror div is the contenteditable.
# The composer mounts late; retry briefly if it isn't in the DOM yet.
for _ in range(10):
    rect = js(r"""
    (()=>{const ce=document.querySelector('div.ProseMirror[contenteditable="true"]');
    if(!ce) return null;
    const r=ce.getBoundingClientRect();return {x:r.x+r.width/2|0,y:r.y+r.height/2|0}})()
    """)
    if rect: break
    wait(0.5)
assert rect, "ProseMirror composer never mounted — page probably failed to hydrate"
click(rect["x"], rect["y"])
wait(0.3)
type_text("Research the top 5 espresso machines under $500 and summarize tradeoffs")
wait(0.4)
# Submit via Enter — the editor has enterkeyhint="enter" and binds Enter to submit.
# (Click the send button at the bottom-right of the composer if Enter is being blocked
#  by an autocomplete / slash-menu.)
press_key("Enter")
wait(3)
print(page_info())  # url will be /app/<taskId>
PY

Composer selectors

The input is a TipTap/ProseMirror contenteditable, not a <textarea>. Setting .value does nothing.

TargetSelectorNotes
Composer containerdiv.flex.flex-col.gap-3.rounded-[22px]Tailwind utility cluster; unique on the page.
Editordiv.tiptap.ProseMirror[contenteditable="true"]Has enterkeyhint="enter". Focus then type via CDP Input.insertText (helpers' type_text()).
Placeholder <p>p[data-placeholder="Assign a task or ask anything"]Visible only when the editor is empty. Goes away after the first keystroke.
Send buttonLast <button> inside the composer (black round, lucide arrow-up icon with empty class).Disabled while the editor is empty; disabled=false once any text is present.
Attach filebutton svg.lucide-plus inside composerFor file uploads (not covered here).
Connect / integrationsbutton svg.lucide-cable inside composerOpens integrations popover.
Computer use togglebutton svg.lucide-monitor inside composerEnables the Cloud PC sandbox for this task.

Submitting via coordinate click on the send button works and is often more reliable than press_key("Enter") — the key-down path occasionally opens the slash-command menu instead of submitting.

Task page (run + result)

python
# Wait for a task to complete
def wait_completed(timeout=1800):
    deadline = time.time() + timeout
    while time.time() < deadline:
        done = js(r"""
        [...document.querySelectorAll('*')].some(el =>
            el.childElementCount < 3 &&
            /^\s*task\s+completed\s*$/i.test((el.innerText||'').trim()))
        """)
        if done: return True
        wait(5)
    return False

Selectors on a running / completed task page:

TargetSelector / patternNotes
User message bubblediv.flex.w-full.flex-col.items-end.justify-end.groupRight-aligned = user. The innermost span.whitespace-pre-wrap carries the text.
Assistant message bodydiv.max-w-none.p-0.m-0.text-[16px].leading-[1.5]Markdown-rendered prose. Code blocks use Shiki (pre:not(.shiki) vs pre.shiki).
Final answer textfirst div.py-[3px].whitespace-pre-wrap.u-break-words under the assistant bodyThe first text block of the final message — good enough for short answers. For long reports, read the whole assistant body's innerText.
"Task completed" markerany element whose trimmed innerText is exactly "Task completed"Paired with a green checkmark SVG (lucide-check). Presence = terminal state.
Suggested follow-upsbutton with a leading svg.lucide-message-circleClickable pills under the final answer; clicking pre-seeds the next prompt.
Sidebar task listleft column, div nodes with text (they are not <a> tags)Active task has a distinct background. Don't query by nav a — there are no anchor links.

Sharing a completed task

bash
browser-harness <<'PY'
# On a /app/<id> page — click the "Share" button (top-right header)
rect = js(r"""
(()=>{const b=[...document.querySelectorAll('button,[role=\"button\"]')].find(x=>x.innerText?.trim()==='Share');
if(!b)return null;const r=b.getBoundingClientRect();return {x:r.x+r.width/2|0,y:r.y+r.height/2|0};})()
""")
click(rect['x'], rect['y'])
wait(1)
# Copy link button inside the popover
rect = js(r"""
(()=>{const b=[...document.querySelectorAll('button')].find(x=>x.innerText?.trim()==='Copy link');
if(!b)return null;const r=b.getBoundingClientRect();return {x:r.x+r.width/2|0,y:r.y+r.height/2|0};})()
""")
click(rect['x'], rect['y'])
wait(0.5)
# The share URL is https://manus.im/share/<same-taskId-as-/app/>
print(page_info()['url'].replace('/app/', '/share/'))
PY

Notes on the Share flow:

  • Clicking Share in the top bar opens a floating popover (div[role="dialog"] inside a [data-floating-ui-portal]). It auto-runs session.v1.SessionService/ShareSession on first open and sets the task to Public access (the checkmark by default). There is an Only me option in the popover if you need to revoke.
  • Clicking Share a second time toggles the popover closed — don't double-click thinking it's a no-op.
  • "Copy link" writes via document.execCommand('copy'), not navigator.clipboard.writeText. If you need to capture the URL, hook both or just compute it: it's https://manus.im/share/<taskId> where the id comes straight from the URL path.
  • ShareSession is idempotent — once a task is public, subsequent calls don't create a new link.

Gotchas

  • The composer is TipTap/ProseMirror. document.querySelector('textarea') returns null on /app and /app/<id>. Use div.tiptap.ProseMirror[contenteditable="true"] or just coordinate-click and type_text().
  • __NEXT_DATA__ is present but empty on /app/<id>. The task state is hydrated via Connect RPC after mount. Don't parse Next's inline JSON for task data — it isn't there.
  • Sidebar nav items are <div>, not <a>. Click by finding the element whose innerText matches "New task" / "Search" / "Library" and using its bounding rect. location.href changes won't be reflected in <a href> attributes.
  • Task titles are auto-generated from the prompt. If the exact prompt text matters for lookup, store the returned taskId (from the URL after submit) — do not grep the sidebar by prompt text.
  • Connect RPC request bodies look binary in a naive fetch-hook. The Connect JS client serializes request JSON into a Uint8Array before handing it to fetch, so body instanceof Uint8Array is true even though the wire content is JSON. Decode with new TextDecoder().decode(body) if you need to see it; content-type (application/json) matches reality.
  • Task submit fires no obvious single "CreateSession" RPC in the page's fetch stream — the initial create is done in the SPA's state transition that routes you to /app/<id>. Follow-on streaming updates arrive through SessionService/GetSession polls (and likely a WebSocket — install a WS hook if you need the wire format).
  • "Task completed" is the only DOM marker for terminal state. Failed / cancelled tasks may surface different text — if you hit one, extend this skill.
  • Cloud PC / Computer Use tasks (when the monitor icon in the composer is toggled) spawn a sandbox VM and stream a VNC-like view into an iframe. Scraping that iframe is out of scope for this skill — see interaction-skills/iframes.md if you need to drive it.
  • Auth wall. If https://manus.im/app redirects to /login or a Google OAuth page, stop and ask the user to sign in. Never type credentials from a screenshot.

Debugging

Install a fetch hook on page load to see what RPC methods fire during a given action:

python
js(r"""
(()=>{
  if(window.__bu_fetch_hooked) return;
  window.__bu_fetch_hooked = true;
  window.__bu_fetch_log = [];
  const of = window.fetch;
  window.fetch = async function(input, init){
    const url = typeof input === 'string' ? input : input.url;
    const r = await of.apply(this, arguments);
    window.__bu_fetch_log.push({t: Date.now(), url, method: (init?.method||'GET').toUpperCase(), status: r.status});
    return r;
  };
})()
""")
# ...do the action...
print(js("window.__bu_fetch_log.filter(e => e.url.includes('api.manus.im')).slice(-20)"))