feat(adapters): add Cursor and Windsurf (Devin) harness adapters#241
feat(adapters): add Cursor and Windsurf (Devin) harness adapters#241CodeGhost21 wants to merge 5 commits into
Conversation
Registers a 'cursor' adapter in plugin-tinyplace mirroring codex/claude, so the one-plugin-any-harness core can run inside Cursor. Grounded in an empirical spike (2026-07-10): cursor-agent spawns MCP servers with a sanitized env (no session id / workspace / CURSOR_* signal, nothing inherited), so: - detection is self-provisioned via TINYPLACE_HARNESS=cursor + TINYPLACE_CURSOR_HOME written into the mcp.json env block; - launch.prepare() writes an isolated <dataDir>/cursor-home/<wallet>/ .cursor/mcp.json carrying the full env block (never touches ~/.cursor); - responder drives headless `cursor-agent -p --model <m>`; - inbound is pull-only (inbox tool); hook-based surfacing is a follow-up. Covered by the adapter-contract test (auto) + harness-test detection/wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registers a 'windsurf' adapter in plugin-tinyplace, mirroring the cursor adapter's GUI-IDE shape (self-provisioned detection, isolated-workspace mcp_config.json install, pull-only inbound, headless responder). Harness key stays 'windsurf' (matches OpenHuman's recognition key + the on-disk ~/.codeium/windsurf/ brand); the responder uses the 'devin' CLI per the 2026-06 Windsurf->Devin Desktop rebrand. BUILD-FROM-DOCS: not empirically verified (no Windsurf/Devin install available). Behavior sourced from docs.devin.ai; [VERIFY]-tagged items (MCP-subprocess env, default model id, global-vs-project mcp_config resolution, a least-privilege responder flag) must be confirmed against a live install before production. Covered by the adapter-contract + harness tests today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@CodeGhost21 is attempting to deploy a commit to the Vezures Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughCursor and Windsurf adapters are added with isolated MCP workspace setup, pull-only inbound handling, responder launch configuration, runtime harness detection, adapter wiring tests, and bounded responder execution with failure handling. ChangesHarness adapter support
Responder execution lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Harness
participant cursorAdapter
participant windsurfAdapter
participant IsolatedWorkspace
participant CursorAgent
participant DevinDesktop
participant MCPServer
Harness->>cursorAdapter: resolve Cursor harness
cursorAdapter->>IsolatedWorkspace: create wallet-scoped .cursor/mcp.json
IsolatedWorkspace-->>cursorAdapter: return workspace path
cursorAdapter->>CursorAgent: launch with workspace
CursorAgent->>MCPServer: load MCP configuration
Harness->>windsurfAdapter: resolve Windsurf harness
windsurfAdapter->>IsolatedWorkspace: create wallet-scoped mcp_config.json
IsolatedWorkspace-->>windsurfAdapter: return workspace path
windsurfAdapter->>DevinDesktop: launch with isolated workspace
DevinDesktop->>MCPServer: load MCP configuration
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a755eeb372
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // tool. NOTE: cursor-agent print-mode can hang after replying (verified) — the | ||
| // shared responder spawner must wrap this call with a timeout/kill. | ||
| buildArgs(prompt, model /* pluginRoot unused: MCP comes from the workspace mcp.json */) { | ||
| return ["-p", "--force", "--approve-mcps", "--output-format", "text", "--model", model, prompt]; |
There was a problem hiding this comment.
Keep Cursor auto-replies out of write mode
When a Cursor session receives a queued DM, hooks/respond-batch.mjs passes the untrusted message text into this buildArgs command as the prompt. Cursor's headless CLI docs state that --force lets print mode make direct file changes without confirmation and the MCP docs state --approve-mcps skips MCP approval (https://cursor.com/docs/cli/headless.md, https://cursor.com/docs/cli/mcp.md), so a prompt-injected DM is not confined to the auto_reply path and can write through the Cursor agent before replying. Use a read-only/sandboxed mode or a tool allowlist here instead of --force.
Useful? React with 👍 / 👎.
| // production, find and add devin's sandbox/tool-restriction equivalent so the only | ||
| // side-effecting path is the tinyplace `auto_reply` MCP tool. | ||
| buildArgs(prompt, model /* pluginRoot unused: MCP comes from mcp_config.json */) { | ||
| return ["-p", "--permission-mode", "bypass", "--model", model, prompt]; |
There was a problem hiding this comment.
Keep Devin auto-replies out of bypass mode
When a Windsurf/Devin session receives a queued DM, this command runs the DM body as a headless prompt. Devin docs define bypass as auto-approving all tool calls, including writes and shell commands, and recommend --sandbox for unattended execution with OS-enforced limits (https://docs.devin.ai/cli/essential-commands, https://docs.devin.ai/cli/reference/permissions); with --permission-mode bypass, a prompt-injected DM can execute or write outside the intended auto_reply path.
Useful? React with 👍 / 👎.
| // shape to reconcile once a live install confirms the resolution order. | ||
| launch: { | ||
| displayHarness: "Windsurf", | ||
| binary: "windsurf", |
There was a problem hiding this comment.
Launch Devin Desktop with its current shell command
The launcher uses launch.binary in installedHarnesses() and prepare() returns the same command below, but current Devin Desktop docs say the folder-opening shell command is devin-desktop while the terminal agent CLI is devin (https://docs.devin.ai/desktop/devin-desktop-faq, https://docs.devin.ai/cli). On a current Devin Desktop install there is no windsurf binary, so this adapter is not auto-offered and a forced launch fails before the generated MCP config can be used.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdk/plugin-tinyplace/adapters/cursor.mjs`:
- Around line 52-64: Update the responder flow in respond-batch.mjs to guard
each cursor-agent process created for the responder configuration with a timeout
and forced termination. Ensure the timeout covers post-reply hangs, rejects or
reports the stalled invocation, and allows the worker to continue processing
subsequent messages; preserve normal output and cleanup behavior for successful
runs.
In `@sdk/plugin-tinyplace/adapters/windsurf.mjs`:
- Around line 63-72: Update the Devin command construction in buildArgs to
remove --permission-mode bypass and use Devin’s sandbox or deny-by-default
permission configuration instead, allowing only the tinyplace auto_reply MCP
tool as a side-effecting capability while preserving the existing prompt and
model arguments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 647cf25a-957f-4782-9a3b-2b5aff9319b8
📒 Files selected for processing (4)
sdk/plugin-tinyplace/adapters/cursor.mjssdk/plugin-tinyplace/adapters/windsurf.mjssdk/plugin-tinyplace/harness-test.mjssdk/plugin-tinyplace/mcp/harness.mjs
…ktop launch Addresses CodeRabbit/Codex review on tinyhumansai#241: - cursor responder: drop --force (auto-allows writes/shell), use --sandbox enabled so a prompt-injected DM can't write/execute through the Cursor agent; --approve-mcps still lets the auto_reply MCP tool through (separate process, outside the sandbox). [P1] - windsurf responder: drop --permission-mode bypass (auto-approves all tools), use Devin's --sandbox OS-level isolation. [P1] - respond-batch: bound every responder turn with a timeout + SIGTERM/ SIGKILL (cursor-agent print-mode can hang after replying, which would wedge a worker); tunable via TINYPLACE_RESPONDER_TIMEOUT_MS. - windsurf launch: binary windsurf -> devin-desktop (current Devin Desktop folder-opener; no windsurf binary exists, so the adapter was never auto-offered). [P2] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdk/plugin-tinyplace/adapters/windsurf.mjs`:
- Around line 63-76: The buildArgs method must terminate CLI option parsing
before passing the attacker-controlled prompt. Update its returned argument list
to include the standalone "--" immediately before prompt, preserving the
existing flags and model arguments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c13aa6ee-6bc8-437d-beea-d97c8f07a9fa
📒 Files selected for processing (3)
sdk/plugin-tinyplace/adapters/cursor.mjssdk/plugin-tinyplace/adapters/windsurf.mjssdk/plugin-tinyplace/hooks/respond-batch.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
- sdk/plugin-tinyplace/adapters/cursor.mjs
The auto-responder passes attacker-controlled DM text as the trailing positional prompt. Without a -- terminator a DM starting with -/-- is parsed as a CLI flag (verified live: cursor-agent errors 'unknown option' on a dash-leading prompt; with -- it's taken as the prompt). Insert -- in both the cursor and windsurf responder buildArgs. Same commander-style parser on devin, so the guard applies there too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
oxoxDev
left a comment
There was a problem hiding this comment.
Review — Cursor + Windsurf harness adapters
Solid harness-core wiring — Cursor path is genuinely E2E-verified, detection is unambiguous and crash-safe, config writes are injection-safe (JSON.stringify + 0o600), and the respond-batch.mjs refactor preserves codex/claude exit semantics byte-for-byte (idempotent settled-guarded finish()). Requesting changes on a few points before merge — the Cursor half is close, but the Windsurf responder's safety posture is unverified, not at proven parity with codex/claude.
Sanity-level parity (Windsurf vs codex/claude)
| Guard | claude | codex | cursor | windsurf |
|---|---|---|---|---|
| UNTRUSTED-data prompt framing | ✓ | ✓ | ✓ | ✓ |
| OS sandbox | --permission-mode dontAsk |
--sandbox read-only |
--sandbox enabled |
--sandbox |
| Never dangerous-bypass | ✓ | ✓ | ✓ | ✓ (documented) |
| Hard tool-allowlist (auto_reply only) | ✓ | ✗ | ✗ | ✗ |
Windsurf inherits the UNTRUSTED framing and documents codex-parity sandboxing, but its actual sandbox invocation is the [VERIFY] line — so least-privilege is claimed, not confirmed. Please close that before enabling it.
Please address
1. [major] adapters/windsurf.mjs:77 — --sandbox shape is unverified, and it's a SECURITY guard, not just a functional flag. buildArgs emits ["-p","--sandbox","--model",model,"--",prompt]. Cursor's verified form is value-taking (--sandbox enabled) and codex is --sandbox read-only. If Devin's --sandbox also takes a value, it consumes --model as the sandbox value and model as a stray positional → the responder either mis-parses (fails closed into failed/) or Devin runs the prompt-injected DM without the intended read-only sandbox — i.e. Windsurf would NOT be at the codex/claude least-privilege parity the comment claims. Self-flagged [VERIFY] + inert-until-provisioned, so not a blocker for the Cursor path — but confirm against a live Devin that (a) the flag parses and (b) it actually enforces read-only, before Windsurf is enabled anywhere. Cleanest: split Windsurf to a follow-up so Cursor lands verified and Windsurf merges only once its sandbox is proven.
2. [minor, security] Responder auto-approves the full tinyplace tool set. --approve-mcps (Cursor) / autonomous -p (Devin) approve every exposed MCP tool — send, contact_add, contact_accept, send_and_wait — not just auto_reply, and TINYPLACE_SEND_ONLY only changes mailbox/presence, not the registered tool set. So the only barrier stopping a prompt-injected inbound DM from messaging third parties or accepting contacts is the soft prompt instruction ("use no other tool"). Claude hard-enforces via --allowedTools mcp__tinyplace__auto_reply; Cursor/Devin headless may not have an equivalent. This is pre-existing (codex's exec responder has the same exposure) — not introduced here — but Cursor/Windsurf inherit it. Recommended hardening (fixes codex too): when TINYPLACE_SEND_ONLY is set, register only auto_reply in the MCP server, instead of relying on per-CLI allowlists.
3. [nit] cursor.mjs responder cwd. The responder is spawned with no cwd/--workspace, so cursor-agent must discover .cursor/mcp.json from the inherited cwd. Confirm the daemon's spawn cwd is the isolated workspace — a wrong cwd silently drops auto_reply with no error.
4. [doc] PR body. The respond-batch.mjs change adds a uniform 180s turn cap (SIGTERM→3s→SIGKILL) to all harnesses incl. codex/claude — a real behavioral delta (their turns were previously unbounded). Net-positive, but worth a line so it's not a silent change.
Verified good
- No regression to codex/claude detection —
harness.mjsdiff purely additive, new branches sit before theclaudedefault. - Detection unambiguous + crash-safe (
TINYPLACE_HARNESS?.trim().toLowerCase(), unique home var per harness, tested); Windsurf is inert unless provisioned → cannot hijack a real cursor/codex/claude session. - Config-write injection-safe;
--prompt terminator on both buildArgs; full adapter-contract conformance;harness-test.mjscovers both new adapters. - Confirmed resolved: Codex "keep Cursor/Devin out of write/bypass mode", Devin binary reconciliation, CR "no timeout/kill guard" (this PR's timeout).
… [VERIFY] Keep the sandboxed least-privilege responder the review asked for (--sandbox enabled, no --force) and add --trust so headless mode can start in the isolated workspace without --force's run-everything. Corrects an earlier over-claim: whether cursor-agent can invoke MCP tools UNDER --sandbox headlessly is NOT yet live-confirmed (validation was blocked by cursor-agent rate-limiting). Documented as [VERIFY] with the fallback if a clean-env retest shows the sandbox blocks the tool call. The first clean E2E confirmed the adapter itself works; the -- terminator and the respond-batch timeout guard remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds Cursor and Windsurf (Devin Desktop) harness adapters to
plugin-tinyplace, so the one-plugin-any-harness core can run inside those agents — the session-origination side of OpenHuman issue #4730 (the recognition side is a separate openhuman PR).Each adapter is a per-harness descriptor registered in
mcp/harness.mjs'sADAPTERSmap, followingadapters/README.mdand mirroringcodex.mjs/claude.mjs.What's new
adapters/cursor.mjs(providercursor) — empirically verified + live-E2E'd.adapters/windsurf.mjs(providerwindsurf) — build-from-docs, not yet live-verified.mcp/harness.mjs: register both + self-provisioned detection.harness-test.mjs: detection + wiring assertions for both (TDD).Design notes (why these differ from codex/claude)
Both Cursor and Windsurf/Devin are GUI-IDE agents, so:
foregroundInject(tmux) is out → inbound is pull-only (theinboxtool); hook-based surfacing is a planned follow-up.cursor-agenthands the MCP server onlyHOME/PATH/SHELL/…; no session id, workspace, orCURSOR_*, nothing inherited). So detection is self-provisioned: install writesTINYPLACE_HARNESS=<harness>+TINYPLACE_<HARNESS>_HOMEinto the MCP configenvblock, which is also the only channel config reaches the server.launch.prepare()writes an isolated workspace MCP config underdataDir(Cursor:.cursor/mcp.json; Windsurf:.codeium/windsurf/mcp_config.json) — never touches the user's real global config.cursor-agent -p --model <m> --force --approve-mcps; Windsurfdevin -p --model <m> --permission-mode bypass.Verification
npm test(harness, adapter-contract for all 5 adapters, envelope, session-info, registry, routing, lock, inject, hooks, mcp-smoke).cursor-agentturn loaded the plugin via the adapter's generatedmcp.json, calledwhoami(reported session labelcursor:1— proving the cursor adapter was selected), and published a Signal identity tostaging-api.tiny.place(independently confirmed via/directory/agents+/keys/{id}/bundle).[VERIFY]before relying on itNo Windsurf/Devin install was available, so
adapters/windsurf.mjsis sourced fromdocs.devin.aiand every unverified assumption is tagged[VERIFY]in-code:devin models),mcp_config.jsonor only the global one (affects install),--permission-mode bypassgrants broad tool access; needs a--sandbox read-onlyequivalent before production (security follow-up).Note: the harness key stays
windsurf(matches OpenHuman's recognition key + the on-disk~/.codeium/windsurf/brand); only the responder binary follows the 2026-06 Windsurf→Devin Desktop rebrand.🤖 Generated with Claude Code
Summary by CodeRabbit