Skip to content

feat(freestyle): add Freestyle VM integration for Virtual MCP repos#3079

Open
tlgimenes wants to merge 36 commits intomainfrom
tlgimenes/check-freestyle-docs
Open

feat(freestyle): add Freestyle VM integration for Virtual MCP repos#3079
tlgimenes wants to merge 36 commits intomainfrom
tlgimenes/check-freestyle-docs

Conversation

@tlgimenes
Copy link
Copy Markdown
Contributor

@tlgimenes tlgimenes commented Apr 9, 2026

Summary

  • Adds Freestyle VM integration so Virtual MCPs can be linked to GitHub repos
  • New tools: VIRTUAL_MCP_ADD_REPO, VIRTUAL_MCP_RUN_SCRIPT, VIRTUAL_MCP_STOP_SCRIPT
  • Play/Stop button in the shell topbar with script dropdown from package.json
  • Browser Inspector view (sandboxed iframe) for viewing the running app
  • Freestyle resource cleanup on Virtual MCP deletion
  • FREESTYLE_API_KEY env var wired through MeshContext (follows firecrawlApiKey pattern)

New files

  • apps/mesh/src/freestyle/ — client, detect, setup, runtime, types modules
  • apps/mesh/src/tools/virtual/add-repo.ts, run-script.ts, stop-script.ts
  • apps/mesh/src/web/components/freestyle-play-button.tsx
  • apps/mesh/src/web/components/browser-inspector-view.tsx

Test plan

  • Set FREESTYLE_API_KEY env var, add a Bun repo via VIRTUAL_MCP_ADD_REPO tool
  • Verify scripts are cached in metadata and Play dropdown shows them
  • Run a script, confirm VM starts and domain is accessible in Browser Inspector
  • Stop the script, confirm VM is deleted and status resets to idle
  • Delete a Virtual MCP with a linked repo, confirm Freestyle resources are cleaned up
  • Verify type checks, lint, and formatting all pass

🤖 Generated with Claude Code


Summary by cubic

Adds Freestyle VM support so Virtual MCPs can link a GitHub repo, auto-detect Bun/Deno scripts, and run them with a live Preview in the in‑app Browser Inspector, with streaming terminal logs during install/startup. Start/stop from the Preview; the sidebar shows a Preview button once a repo is linked.

  • New Features

    • Link a GitHub repo to a Virtual MCP and auto‑detect Bun/Deno scripts/tasks (reads package.json, deno.json/jsonc/lock, AGENTS.md, deco.json).
    • Run scripts on Freestyle VMs with a persistent Preview and live web terminal while installing; VM start is async and the inspector polls during install.
    • GitHub tab in Settings: link/unlink, runtime/autorun pickers, preview port (saves on blur). Scripts/runtime are saved even without FREESTYLE_API_KEY; entity cache auto‑refreshes after tool calls.
    • VM lifecycle and cleanup: create Freestyle repo + snapshot; run on @freestyle-sh/with-bun or @freestyle-sh/with-deno; map preview_port to 443; clean up on MCP delete or unlink. Tools: VIRTUAL_MCP_ADD_REPO, VIRTUAL_MCP_RUN_SCRIPT, VIRTUAL_MCP_STOP_SCRIPT; deps freestyle-sandboxes, @freestyle-sh/with-bun, @freestyle-sh/with-deno, @freestyle-sh/with-web-terminal; new env FREESTYLE_API_KEY (exposed as freestyleApiKey).
  • Bug Fixes

    • Allow starting when status is “running” but vm_domain is missing (stale state); treat empty vm_domain as null; run-script returns a nullable domain.
    • Use correct Deno integration key (“deno”) and run via deno task; split VM creation per runtime; switch to flat vms.create options and snapshot so domains resolve.
    • Make VM creation async to prevent MCP timeouts; clearer step‑by‑step setup errors and diagnostics for startup/domain resolution; construct domain from vmId if none is returned.
    • Update metadata with terminal_domain immediately after VM creation so Preview shows the live terminal during install; adjust tests to include terminal_domain.
    • Resolve rebase issues in shell/layout; fix Browser Inspector import and view registration; remove unused freestyle-play-button component.

Written for commit 7f4bc6b. Summary will update on new commits.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Release Options

Suggested: Minor (2.251.0) — based on feat: prefix

React with an emoji to override the release type:

Reaction Type Next Version
👍 Prerelease 2.250.6-alpha.1
🎉 Patch 2.250.6
❤️ Minor 2.251.0
🚀 Major 3.0.0

Current version: 2.250.5

Note: If multiple reactions exist, the smallest bump wins. If no reactions, the suggested bump is used (default: patch).

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

8 issues found across 23 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/web/routes/agent-home.tsx">

<violation number="1" location="apps/mesh/src/web/routes/agent-home.tsx:119">
P2: `browser-inspector` is not handled in `useResolvedMainView`, so metadata defaults to chat instead of opening the inspector.</violation>
</file>

<file name="apps/mesh/src/tools/virtual/add-repo.ts">

<violation number="1" location="apps/mesh/src/tools/virtual/add-repo.ts:66">
P1: The catch block restores stale metadata after cleanup, which can persist deleted Freestyle IDs and mismatched repo/runtime data.</violation>
</file>

<file name="apps/mesh/src/freestyle/runtime.ts">

<violation number="1" location="apps/mesh/src/freestyle/runtime.ts:28">
P1: `stopScript` should surface VM deletion failures instead of ignoring them; otherwise stop operations can falsely succeed.</violation>
</file>

<file name="apps/mesh/src/freestyle/setup.ts">

<violation number="1" location="apps/mesh/src/freestyle/setup.ts:30">
P1: `setupRepo` lacks compensating cleanup on partial failure, which can orphan Freestyle repos/VMs.</violation>

<violation number="2" location="apps/mesh/src/freestyle/setup.ts:69">
P2: Cleanup currently swallows deletion failures, making leaked Freestyle resources invisible.</violation>
</file>

<file name="apps/mesh/src/tools/virtual/run-script.ts">

<violation number="1" location="apps/mesh/src/tools/virtual/run-script.ts:64">
P1: The “optimistic lock” is not actually atomic; concurrent run requests can both start VMs and leak orphaned instances.</violation>

<violation number="2" location="apps/mesh/src/tools/virtual/run-script.ts:91">
P1: If metadata persistence fails after VM creation, the catch path does not delete the created VM, which can leak running Freestyle instances.</violation>
</file>

<file name="apps/mesh/src/tools/virtual/delete.ts">

<violation number="1" location="apps/mesh/src/tools/virtual/delete.ts:64">
P2: Do not swallow Freestyle cleanup failures with an empty catch; at minimum, surface/log the error so orphaned resource leaks are detectable.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

if (metadata.freestyle_vm_id) {
await freestyle.vms
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P1: stopScript should surface VM deletion failures instead of ignoring them; otherwise stop operations can falsely succeed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/runtime.ts, line 28:

<comment>`stopScript` should surface VM deletion failures instead of ignoring them; otherwise stop operations can falsely succeed.</comment>

<file context>
@@ -0,0 +1,80 @@
+  }
+
+  if (metadata.freestyle_vm_id) {
+    await freestyle.vms
+      .delete({ vmId: metadata.freestyle_vm_id })
+      .catch(() => {});
</file context>
Fix with Cubic


if (metadata.freestyle_vm_id) {
promises.push(
freestyle.vms.delete({ vmId: metadata.freestyle_vm_id }).catch(() => {}),
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: Cleanup currently swallows deletion failures, making leaked Freestyle resources invisible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/setup.ts, line 69:

<comment>Cleanup currently swallows deletion failures, making leaked Freestyle resources invisible.</comment>

<file context>
@@ -0,0 +1,82 @@
+
+  if (metadata.freestyle_vm_id) {
+    promises.push(
+      freestyle.vms.delete({ vmId: metadata.freestyle_vm_id }).catch(() => {}),
+    );
+  }
</file context>
Fix with Cubic

await cleanupFreestyleResources(
freestyle,
existing.metadata as Record<string, unknown>,
).catch(() => {});
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: Do not swallow Freestyle cleanup failures with an empty catch; at minimum, surface/log the error so orphaned resource leaks are detectable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/tools/virtual/delete.ts, line 64:

<comment>Do not swallow Freestyle cleanup failures with an empty catch; at minimum, surface/log the error so orphaned resource leaks are detectable.</comment>

<file context>
@@ -53,6 +55,15 @@ export const COLLECTION_VIRTUAL_MCP_DELETE = defineTool({
+      await cleanupFreestyleResources(
+        freestyle,
+        existing.metadata as Record<string, unknown>,
+      ).catch(() => {});
+    }
+
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/web/views/virtual-mcp/index.tsx">

<violation number="1" location="apps/mesh/src/web/views/virtual-mcp/index.tsx:1059">
P2: Unwrap the `VIRTUAL_MCP_ADD_REPO` tool response so structured tool errors are not silently treated as success.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/tools/virtual/add-repo.ts">

<violation number="1" location="apps/mesh/src/tools/virtual/add-repo.ts:113">
P2: Do not swallow the metadata reset failure; silently ignoring this update can leave the Virtual MCP stuck in `installing` state and hide the real storage issue.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/freestyle/setup.ts">

<violation number="1" location="apps/mesh/src/freestyle/setup.ts:73">
P2: Clean up created Freestyle resources when dependency installation fails; otherwise failed setup attempts can leak orphaned VM/repo resources.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

try {
await vm.vm.js.install({ directory: "/app" });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: Clean up created Freestyle resources when dependency installation fails; otherwise failed setup attempts can leak orphaned VM/repo resources.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/setup.ts, line 73:

<comment>Clean up created Freestyle resources when dependency installation fails; otherwise failed setup attempts can leak orphaned VM/repo resources.</comment>

<file context>
@@ -56,30 +56,28 @@ export async function setupRepo(
 
-  await vm.suspend();
+  try {
+    await vm.vm.js.install({ directory: "/app" });
+  } catch (e) {
+    throw new Error(
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 14 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx">

<violation number="1" location="apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx:163">
P1: Do not ignore stop-script failures during unlink; clearing metadata after a failed stop can orphan a running VM.</violation>

<violation number="2" location="apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx:295">
P2: Persisting preview_port on each keystroke causes repeated mutations/invalidation; use a local draft value and save on blur/submit instead.</violation>
</file>

<file name="apps/mesh/src/freestyle/runtime.ts">

<violation number="1" location="apps/mesh/src/freestyle/runtime.ts:41">
P2: Validate `preview_port` before using it as the VM target port and in the shell command; persisted metadata is not guaranteed to be a valid port at runtime.</violation>
</file>

<file name="apps/mesh/src/freestyle/parse-metadata.ts">

<violation number="1" location="apps/mesh/src/freestyle/parse-metadata.ts:30">
P2: Validate `scripts` object entries before casting; currently non-string values are accepted as `Record<string, string>` and can break downstream string usage.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

name: "VIRTUAL_MCP_STOP_SCRIPT",
arguments: { virtual_mcp_id: virtualMcp.id },
})
.catch(() => {});
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P1: Do not ignore stop-script failures during unlink; clearing metadata after a failed stop can orphan a running VM.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx, line 163:

<comment>Do not ignore stop-script failures during unlink; clearing metadata after a failed stop can orphan a running VM.</comment>

<file context>
@@ -0,0 +1,334 @@
+            name: "VIRTUAL_MCP_STOP_SCRIPT",
+            arguments: { virtual_mcp_id: virtualMcp.id },
+          })
+          .catch(() => {});
+      }
+
</file context>
Fix with Cubic

.workdir("/app")
.waitForReadySignal(true);

const targetPort = metadata.preview_port ?? 3000;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: Validate preview_port before using it as the VM target port and in the shell command; persisted metadata is not guaranteed to be a valid port at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/runtime.ts, line 41:

<comment>Validate `preview_port` before using it as the VM target port and in the shell command; persisted metadata is not guaranteed to be a valid port at runtime.</comment>

<file context>
@@ -38,18 +38,20 @@ export async function runScript(
     .workdir("/app")
     .waitForReadySignal(true);
 
+  const targetPort = metadata.preview_port ?? 3000;
+
   const { vm, vmId, domains } = await freestyle.vms.create({
</file context>
Suggested change
const targetPort = metadata.preview_port ?? 3000;
const targetPort =
typeof metadata.preview_port === "number" &&
Number.isInteger(metadata.preview_port) &&
metadata.preview_port >= 1 &&
metadata.preview_port <= 65535
? metadata.preview_port
: 3000;
Fix with Cubic

running_script:
typeof m.running_script === "string" ? m.running_script : null,
vm_domain: typeof m.vm_domain === "string" ? m.vm_domain : null,
scripts:
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: Validate scripts object entries before casting; currently non-string values are accepted as Record<string, string> and can break downstream string usage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/parse-metadata.ts, line 30:

<comment>Validate `scripts` object entries before casting; currently non-string values are accepted as `Record<string, string>` and can break downstream string usage.</comment>

<file context>
@@ -0,0 +1,63 @@
+    running_script:
+      typeof m.running_script === "string" ? m.running_script : null,
+    vm_domain: typeof m.vm_domain === "string" ? m.vm_domain : null,
+    scripts:
+      m.scripts && typeof m.scripts === "object" && !Array.isArray(m.scripts)
+        ? (m.scripts as Record<string, string>)
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx">

<violation number="1" location="apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx:148">
P2: `portInput` is seeded from `fm.preview_port` but never re-synced when server metadata changes, so the Preview port field can display stale values.

(Based on your team's feedback about syncing props-derived draft inputs when async server values change.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const fm = parseFreestyleMetadata(virtualMcp.metadata);
const [unlinkOpen, setUnlinkOpen] = useState(false);
const [unlinking, setUnlinking] = useState(false);
const [portInput, setPortInput] = useState(
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: portInput is seeded from fm.preview_port but never re-synced when server metadata changes, so the Preview port field can display stale values.

(Based on your team's feedback about syncing props-derived draft inputs when async server values change.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/views/virtual-mcp/github-tab-content.tsx, line 148:

<comment>`portInput` is seeded from `fm.preview_port` but never re-synced when server metadata changes, so the Preview port field can display stale values.

(Based on your team's feedback about syncing props-derived draft inputs when async server values change.) </comment>

<file context>
@@ -145,6 +145,9 @@ function PopulatedState({ virtualMcp }: { virtualMcp: VirtualMCPEntity }) {
   const fm = parseFreestyleMetadata(virtualMcp.metadata);
   const [unlinkOpen, setUnlinkOpen] = useState(false);
   const [unlinking, setUnlinking] = useState(false);
+  const [portInput, setPortInput] = useState(
+    fm.preview_port != null ? String(fm.preview_port) : "",
+  );
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 10 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/freestyle/detect.test.ts">

<violation number="1" location="apps/mesh/src/freestyle/detect.test.ts:78">
P2: The `deno.jsonc` test uses JSON instead of JSONC, so it doesn’t actually validate JSONC parsing.</violation>
</file>

<file name="packages/mesh-sdk/src/types/virtual-mcp.ts">

<violation number="1" location="packages/mesh-sdk/src/types/virtual-mcp.ts:216">
P2: Create/update now allow `runtime: "deno"`, but the entity schema still only allows `"bun"`, causing inconsistent validation and potential parse failures for stored entities.</violation>
</file>

<file name="apps/mesh/src/freestyle/setup.ts">

<violation number="1" location="apps/mesh/src/freestyle/setup.ts:11">
P2: `SetupResult.runtime` now claims Deno support, but `setupRepo` still provisions and installs with `VmBun` unconditionally. This creates a runtime/contract mismatch for Deno-detected repositories.</violation>
</file>

<file name="apps/mesh/src/freestyle/detect.ts">

<violation number="1" location="apps/mesh/src/freestyle/detect.ts:83">
P2: `deno.jsonc` is parsed with `JSON.parse`, so valid JSONC configs can be ignored and task scripts will appear empty.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const result = await detectRepo(
"owner/repo",
mockReader({
"deno.jsonc": JSON.stringify({ tasks: { dev: "deno run dev.ts" } }),
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: The deno.jsonc test uses JSON instead of JSONC, so it doesn’t actually validate JSONC parsing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/detect.test.ts, line 78:

<comment>The `deno.jsonc` test uses JSON instead of JSONC, so it doesn’t actually validate JSONC parsing.</comment>

<file context>
@@ -49,16 +49,91 @@ describe("detectRepo", () => {
+    const result = await detectRepo(
+      "owner/repo",
+      mockReader({
+        "deno.jsonc": JSON.stringify({ tasks: { dev: "deno run dev.ts" } }),
+      }),
+    );
</file context>
Suggested change
"deno.jsonc": JSON.stringify({ tasks: { dev: "deno run dev.ts" } }),
"deno.jsonc": `{
// dev task
"tasks": {
"dev": "deno run dev.ts",
},
}`,
Fix with Cubic

repoId: string;
snapshotId: string;
vmId: string;
runtime: "bun" | "deno";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: SetupResult.runtime now claims Deno support, but setupRepo still provisions and installs with VmBun unconditionally. This creates a runtime/contract mismatch for Deno-detected repositories.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/setup.ts, line 11:

<comment>`SetupResult.runtime` now claims Deno support, but `setupRepo` still provisions and installs with `VmBun` unconditionally. This creates a runtime/contract mismatch for Deno-detected repositories.</comment>

<file context>
@@ -8,7 +8,7 @@ export interface SetupResult {
   snapshotId: string;
   vmId: string;
-  runtime: "bun";
+  runtime: "bun" | "deno";
   scripts: Record<string, string>;
   instructions: string | null;
</file context>
Fix with Cubic

const raw = denoJsonRaw ?? denoJsoncRaw;
if (raw) {
try {
const denoConfig = JSON.parse(raw);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 9, 2026

Choose a reason for hiding this comment

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

P2: deno.jsonc is parsed with JSON.parse, so valid JSONC configs can be ignored and task scripts will appear empty.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/freestyle/detect.ts, line 83:

<comment>`deno.jsonc` is parsed with `JSON.parse`, so valid JSONC configs can be ignored and task scripts will appear empty.</comment>

<file context>
@@ -52,6 +56,51 @@ function parseDecoJson(raw: string): DecoJson | null {
+    const raw = denoJsonRaw ?? denoJsoncRaw;
+    if (raw) {
+      try {
+        const denoConfig = JSON.parse(raw);
+        return denoConfig.tasks ?? {};
+      } catch {
</file context>
Fix with Cubic

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai bot commented Apr 9, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

tlgimenes and others added 20 commits April 9, 2026 12:27
Virtual MCPs can now be linked to GitHub repos via Freestyle VMs.
Adds tools (ADD_REPO, RUN_SCRIPT, STOP_SCRIPT), Play button in
topbar, browser inspector view, and resource cleanup on deletion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r tool calls

Adds owner/repo input field in the Virtual MCP instructions tab.
Play/Stop/AddRepo tool calls now invalidate the entity query cache
so the UI updates immediately after state changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wraps each Freestyle API call (detect, repo create, github sync,
VM create) in its own try/catch so errors surface which step
failed instead of a generic "Unknown error code" message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GitHub sync requires a GitHub App to be installed on the target repo.
Skip silently if it fails — the repo is already cloned via source URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r deps

systemd services can't find bun binary (installed at /opt/bun/bin/bun).
Use vm.js.install() from VmBun integration for dep installation, and
use full bun path with nohup for running scripts in the background.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move repo configuration from Instructions tab to a dedicated GitHub tab
with empty state (connect input) and populated state (runtime, autorun,
preview port settings). Add Preview button in tasks panel sidebar that
opens browser inspector when preview port is configured.

Key changes:
- GitHub tab always visible in settings (Instructions/GitHub/Connections/Layout)
- preview_port passed as targetPort to Freestyle VM (ports 443 mapping)
- deco.json read from repo during setup with validation
- Shared useInvalidateVirtualMcp hook and parseFreestyleMetadata utility
- Unlink action with confirmation dialog clears all 11 freestyle fields
- External link in browser inspector uses noopener/noreferrer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… every keystroke

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detect deno.json, deno.jsonc, and deno.lock for Deno projects.
Uses @freestyle-sh/with-deno VmDeno integration and `deno task`
for script execution. Runtime auto-detected from lockfiles,
overridable via deco.json or the GitHub tab dropdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The entity schema (used for LIST/GET output validation) was still
["bun"] only — the previous replace_all missed it due to different
formatting (multiline vs inline).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows existing scripts with delete buttons, and an inline form to add
new entries. Labels adapt to runtime (scripts for bun, tasks for deno).
This makes the play button work for manually configured repos where
VIRTUAL_MCP_ADD_REPO wasn't used.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scripts are populated automatically from repo detection via
VIRTUAL_MCP_ADD_REPO (package.json scripts or deno.json tasks).
The GitHub tab shows them as read-only badges, not an editable form.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…style setup

Detection (GitHub API) and infra setup (Freestyle VM) are now separate
steps. Scripts, runtime, and instructions are persisted immediately
after detection, so even if Freestyle setup fails (no API key, VM error),
the play button still works because scripts are populated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scripts are only shown in the play button dropdown, not duplicated
in the settings panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Preview button in tasks panel now shows when repo_url is set (no
  preview_port requirement)
- Removed FreestylePlayButton from the topbar header
- Browser inspector empty state now shows script/task buttons to start
  the dev server, with loading and installing states
- Toolbar shows stop button and green status dot when running

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Toolbar always renders with URL bar, refresh, and play/stop controls.
Content area swaps between iframe (when running) and contextual empty
state (with play icon + hint text when idle). Play dropdown with
script selection lives in the toolbar top-right.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parent layout uses items-center which vertically centered the view.
Added self-stretch to override and fill from top.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tlgimenes and others added 13 commits April 9, 2026 12:27
… vm_domain

If runtime_status is "running" but vm_domain is null, the state is
stale from a previous failed run. Allow starting a new script instead
of blocking with an error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Freestyle domains[0] can be empty string when no domain is assigned.
parseFreestyleMetadata now treats "" as null, runtime returns null
instead of "", and run-script output schema accepts nullable domain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Freestyle API expects { snapshot, ports, ... } for the object form of
vms.create(). Using { spec } caused domains to be empty array since
the API didn't recognize the spec parameter for port/domain assignment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VM creation can take 30-60s (Freestyle cache miss). The run-script
tool now returns immediately with installing status, and the VM
creation runs in the background. The browser inspector polls every
3s while installing to detect when the VM is ready and render the
iframe.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n assignment

VmSpec as snapshot wasn't returning domains. Switched to the flat
object form with `with`, `gitRepos`, `workdir`, and `ports` — this
matches the documented API that returns domains in the response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs all keys from create result, tries vms.get() as fallback to
find the domain if create doesn't return it. This will tell us
exactly what Freestyle returns and where the domain lives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… fallback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…log after start

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The VmDeno integration must be registered with key "deno" not "js",
and deno binary should be on PATH (use `deno task` not `/root/.deno/bin/deno`).
Also fixed type overload issue by splitting create calls per runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Knip detected this file as unused — it's not imported anywhere.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the spinner shown during VM install/startup with a live
streaming terminal (ttyd) via Freestyle's VmWebTerminal integration.
Users now see real-time output of bun install and dev server startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tlgimenes tlgimenes force-pushed the tlgimenes/check-freestyle-docs branch from 243e5c6 to a42fadf Compare April 9, 2026 15:28
tlgimenes and others added 3 commits April 9, 2026 12:33
Restore shell-layout.tsx to match main (was incorrectly polluted with
agent-specific code during rebase). Fix browser-inspector-view import to
use agent-shell-layout. Add browser-inspector view type to
agent-shell-layout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n field

The test expected 11 fields but emptyFreestyleMetadata now returns 12
after terminal_domain was added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split runScript into createVm + waitForApp so the terminal domain is
stored in metadata immediately after VM creation. This lets the UI
show the terminal iframe during install instead of the spinner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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