Repository navigation
Add the self-hosted LoopForge platform; move the CLI into cli/ - #46
Robert0622 wants to merge 43 commits into
Conversation
The repository root is about to host the self-hosted LoopForge platform, so the CLI moves to its own directory with paths, packaging metadata and CI working directories adjusted to keep it building and releasing unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
Teams need the same SOP-driven delivery that the CLI gives a single agent, but shared, auditable and running on their own machines, so the platform (server, web studio, runners, REST and MCP APIs) becomes the primary project here under Apache-2.0 while the CLI keeps its MIT license. Co-authored-by: Cursor <cursoragent@cursor.com>
The mirror only worked with an SDK that is not publicly available, so it was dead code for every open-source user; local metrics and the dashboard keep working unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
…oadmap The README now leads with the three things that set LoopForge apart (the automated requirement loop, observable and evaluable runs, expert SOPs with shared context), explains run isolation and the MySQL migration step, and points to a roadmap; operator docs and migration comments lose their internal-history tone. Co-authored-by: Cursor <cursoragent@cursor.com>
Platform tags now also create a "LoopForge Platform" release marked latest, CLI releases are titled "LoopForge CLI" without claiming latest, and platform jobs get prefixed check names so the two release lines and their status checks stay distinguishable. Co-authored-by: Cursor <cursoragent@cursor.com>
Per-run environment isolation is a core promise, but inside the container the platform fell back to local processes. The image now ships the Docker CLI, compose mounts the host socket, and bind sources are translated through the platform container's own mounts so each run gets a fresh container on the host's Docker, joined to the compose network. Co-authored-by: Cursor <cursoragent@cursor.com>
Git 2.5x rejects `git add -u` pathspecs when the index is empty, which failed delivery for freshly initialised repositories on current CI runners; there is nothing to update in that case. Co-authored-by: Cursor <cursoragent@cursor.com>
Merging the two trees left AGENTS.md as a self-referencing include, so agents and contributors got no layout rules; the internal history notes are replaced with how to run and verify the platform. Co-authored-by: Cursor <cursoragent@cursor.com>
The demo SOP simulates every step, but dispatch still attached the project's default repository, so trying the demo after binding a repo failed on a clone it never needed. Co-authored-by: Cursor <cursoragent@cursor.com>
…r in view New users finished the tour without knowing what to do next, and the credential save button sat at the bottom of a long form. The dashboard now shows a live checklist with direct actions and a one-click demo run, the tour starts there, and the save bar sticks to the viewport and flags unsaved edits. Co-authored-by: Cursor <cursoragent@cursor.com>
The built-in mock executor writes its own gate result, yet provisioning still required the gate MCP binary, so demo runs failed on standalone binaries. Missing helpers are now reported at startup with how to fix the install, and a binary inside a checkout's bin/ finds its source from any working directory. Co-authored-by: Cursor <cursoragent@cursor.com>
Apache 2.0 for the platform and MIT for the CLI was a compliance risk; use MIT everywhere and keep third-party notices unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
`make run` / `make demo` now build the gate MCP helpers and point at them, and `./start.sh` pulls GHCR or builds from the checkout with demo mode on so a fresh clone works without inventing a deploy script. Co-authored-by: Cursor <cursoragent@cursor.com>
Users get lost after the tour; a home chatbot lets them clarify in one sentence, dispatch, and watch running or waiting workflows in the same view, with a one-click demo when no API key is set. Co-authored-by: Cursor <cursoragent@cursor.com>
Fixed demo login, a short quick-fix SOP, credential wizard, humanized errors, WeCom notify hooks, and login-free approval share links so users can start without scraping compose logs or babysitting the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the same group-chat webhook behavior; only rename the public docs and comments so platform hygiene stays green. Co-authored-by: Cursor <cursoragent@cursor.com>
./start.sh -y installs missing curl/git/Docker/Compose when possible, pulls GHCR (or builds), waits on /api/health, and prints admin/demo1234. install.sh enables a curl|bash bootstrap without a prior clone. Co-authored-by: Cursor <cursoragent@cursor.com>
…rter start.sh - /api/doctor reports sandbox isolation, sandbox image, gate helper, model key, Git token, CLIs and notifications; the guide shows it with fix buttons and admins can prewarm the sandbox image from there - loopforge --self-test (./start.sh doctor) logs in, runs one demo requirement, approves it through the public share link and checks the delivery report - the default sandbox image builds in the background on first start - start.sh keeps command-line variables over .env, moves to a free port, builds the cloned checkout (release tags pull), falls back to Go/npm mirrors, checks disk space, and adds warm/reset; compose gains a healthcheck and passes the notify/public URL settings Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- built-in agent nodes fall back to the CLI the user has a key for, so the default quick-fix SOP runs with only a CodeBuddy (or Codex) key - saving a CodeBuddy key verifies it against the international and China sites and stores the network environment that works - home chat shows clarification errors instead of an empty "done"; a 401 from the model returns a credential hint at once instead of three retries - the login form shows the demo account while demo mode applies Co-authored-by: Cursor <cursoragent@cursor.com>
- home chat reads the clarification from the done event, shows answer options, and run cards show the requirement title and open its issue - approvals page loads full run snapshots, so it names the waiting node and explains planned approval nodes instead of reporting a failed gate - external approval links show the requirement and the upstream result - demo executor writes a readable sample result instead of echoing the prompt - loopback OTLP export works when login is required Co-authored-by: Cursor <cursoragent@cursor.com>
The quick-fix and demo agents have no task prompt, so their prompt carried only the issue identity block and agents could not see what to build. The run input (and, without a brief, upstream results) is now always included. Dispatch input no longer repeats a title that the description starts with. Co-authored-by: Cursor <cursoragent@cursor.com>
- dispatch refuses a run whose bound GitHub repository cannot be pushed because the responsible user has no git token (and no execution machine) - home chat tells apart a created issue that could not start, and its settings / open-issue buttons now work - executor fallback is recorded in the run telemetry - unbound-repository clarification shows a neutral hint instead of a repository fetch failure; run cards translate waiting / paused - README names the 直接创建 button Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- importing an issue from a public repository reads it anonymously; a failure explains that private repositories need a GitHub token - import errors stay on screen as error toasts - the git-token refusal and doctor hint point to 个人设置 and say when the token is needed Co-authored-by: Cursor <cursoragent@cursor.com>
…tool calls in run evaluation facts Co-authored-by: Cursor <cursoragent@cursor.com>
…xecutor that will actually run Co-authored-by: Cursor <cursoragent@cursor.com>
…quick-fix once a model key is set; localize sync logs Co-authored-by: Cursor <cursoragent@cursor.com>
…oken is set Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…op rerun on approval gates Co-authored-by: Cursor <cursoragent@cursor.com>
…o SOP once a key is set Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…efault to docker Co-authored-by: Cursor <cursoragent@cursor.com>
6ba7a4e to
f5f0b06
Compare
In docker compose the hostname is the container ID, which changes on every recreate. Runs bound before an upgrade then looked foreign: their idle sandbox containers were never reclaimed, and a retry rebuilt the workspace from the remote branch, dropping unpushed changes. Co-authored-by: Cursor <cursoragent@cursor.com>
…at pages - No built-in password: without LOOPFORGE_ADMIN_PASSWORD the first visitor creates the admin in the browser and adopts the ownerless default project. - Self-registration from the login page, open by default; admins toggle it under 平台管理 (LOOPFORGE_SIGNUP=closed sets the default). - 个人设置 is split into 模型 Key / 代码仓库 / MCP 令牌 / 账号 tabs routed as #/settings/<tab>; switching tabs keeps unsaved input. - 发起需求 gets its own page; 工作总览 keeps the checklist and status only. - The tour starts at 发起需求 and ends on the settings tab that still needs a key; the credential wizard no longer pops up by itself. - Self-test signs an admin session inside the deployment instead of using a demo password. - Playwright: first-run setup (second server without an admin), registration and the sign-up switch, settings tabs, account tab, chat/dashboard split. Co-authored-by: Cursor <cursoragent@cursor.com>
…the tab closes - 发起需求: clear the busy state before the final render so the composer unlocks after 建单并开跑, and retire the stale inline dispatch button. - CodeBuddy key verification no longer stops when the browser disconnects, so the detected network environment is still saved. Co-authored-by: Cursor <cursoragent@cursor.com>
…tons Hovering a toast pauses its dismissal, so one sitting on a drawer's close button blocked closing the drawer for as long as the pointer stayed there. Co-authored-by: Cursor <cursoragent@cursor.com>
… panel The page now opens on a centered composer with SOP cards. Clarify questions come back as clickable options with a completeness meter, and a launch card appears once the requirement is clear. Sending a message without a model key explains what is missing instead of starting the demo. Run progress for every active SOP, step by step, lives in a collapsible panel on the right. Co-authored-by: Cursor <cursoragent@cursor.com>
The home chat now talks to the platform assistant in a PM role instead of only clarifying new requirements. It can read attached images and files, @-reference existing requirements, check run progress, propose writes as drafts confirmed through /mcp, return generated files and mermaid diagrams, and still clarify with option questions before offering a launch card. The dashboard view is gone: the brand and the first nav entry open 首页, old #/dashboard links land there, the quick-start checklist moved to the guide page, and the issue drawer gets a button to discuss a requirement on the home page. Mermaid is vendored so diagrams render under the CSP. Co-authored-by: Cursor <cursoragent@cursor.com>
…unctuation, name draft targets Co-authored-by: Cursor <cursoragent@cursor.com>
…launch it twice Co-authored-by: Cursor <cursoragent@cursor.com>
fx-kurumi
left a comment
There was a problem hiding this comment.
Consolidated review (backend, API/security, frontend, build/test)
Reviewed at 55b17d8. go build ./..., go vet ./... and go test ./... pass locally on Go 1.25.0, and all 15 CI checks are green. go test -race and coverage were not completed, so concurrency is not race-detector verified.
Important
- Busy loop after cancellation in
internal/runner/exec.go(~L86 and ~L336): oncectxis done theselectkeeps firing; at L336 this also re-callsterminateProcessGroupon every iteration (new Wait goroutine, repeated SIGTERM, up to 10s block), andcmd.Wait()ends up called twice. - Unauthenticated first-admin setup (
internal/httpapi/auth_signup.go): whoever reaches/api/auth/setupfirst becomes admin and adopts all ownerless projects. Please require a one-time setup token or restrict to loopback. - Auth is off by default in
authRequired()(internal/httpapi/auth_tokens.go); onlycmd/loopforgeforces it on. Please invert the default and disable explicitly in tests. - Vendored Monaco (~24MB) without version/provenance in
web/monaco/, embedded whole viaweb/embed.go. Consider fetching viamake web-depswith a pinned version + checksum, like vscode-web; otherwise add a VERSION/README. Same forweb/vendor/mermaid(v11.17.2).
Suggestions
internal/engine/node_execute.go: wave-based scheduling (wg.Wait()per round) lets one slow node block ready successors; 80ms polling ofRunStatewhen idle.cmd/loopforge/main.go: mutates package globaldomain.ForgeRepoHostat runtime (prefer config injection); 30-min worker drain default exceeds typical 30s container grace periods.internal/httpapi/auth.go: trustX-Forwarded-Protoonly fromtrustedProxyPeer. Signup check-then-create is racy (auth_signup.go). CORS check ignores scheme (gin_helpers.go).- Frontend: unescaped
a.id/a.versioninweb/app/agents.js; Markdown links double-escaped inweb/app.js; ~230innerHTMLstring builds overall, consider DOM APIs/templates long term; inconsistent?v=cache-busting inindex.html. - Tests: no tests in
cmd/loopforge-runner,cmd/loopforge-mcp,cmd/loopforge-gate-mcp,internal/apperr,internal/application. Please add tests for the runner cancellation fix and for auth-on-by-default / setup protection.
Not fully covered
Per-project authorization across handlers, managedcli, tracker, docs/api, install.sh / legacy install-path compatibility after moving the CLI to cli/, and in-browser testing of web/app/*.
| canceled := false | ||
| for { | ||
| select { | ||
| case <-ctx.Done(): |
There was a problem hiding this comment.
After ctx is done this case fires on every iteration, so the loop spins until sandbox.Exec returns, and the flush/renew tickers keep using the dead ctx. Suggest setting the channel to nil after the first hit (or guarding with if !canceled).
| for { | ||
| select { | ||
| case <-ctx.Done(): | ||
| canceled = true |
There was a problem hiding this comment.
Same busy loop, but heavier: terminateProcessGroup runs on every iteration (spawning another cmd.Process.Wait() goroutine, sending SIGTERM again, blocking up to cancelGrace), and cmd.Wait() below is a second Wait on the same process. Terminate once and leave reaping to the cmd.Wait() below.
| setupMu.Lock() | ||
| defer setupMu.Unlock() | ||
| if h.repo.HasPasswordAccount() { | ||
| c.JSON(http.StatusConflict, gin.H{"error": "管理员已创建,请直接登录"}) |
There was a problem hiding this comment.
Unauthenticated: if the instance is reachable before the admin is created, anyone can claim admin and adopt all ownerless projects. Suggest a one-time setup token (logged at startup or from env) or loopback-only setup.
| func authRequired() bool { | ||
| value := strings.TrimSpace(os.Getenv("LOOPFORGE_AUTH_REQUIRED")) | ||
| if value == "" { | ||
| return false |
There was a problem hiding this comment.
Auth defaults to off when the env var is unset; only cmd/loopforge forces it on. Other entry points or embedders building the router get no auth. Please default to true and disable explicitly in tests.
| @@ -0,0 +1,203 @@ | |||
| function renderAgentsLegacy(){ | |||
| const list=$('#agentList');list.innerHTML=state.data.agents.map(a=>`<div class="agent-card ${state.selectedAgent===a.id?'active':''}" data-id="${a.id}"><div class="agent-card-top"><h3>${escapeHTML(a.name)}</h3><span class="pill">v${a.version}</span></div><p>${escapeHTML(a.description||'暂无说明')}</p><div class="chips"><span class="chip">${a.rules?.length||0} rules</span><span class="chip">${a.skills?.length||0} skills</span><span class="chip">${a.mcpServers?.length||0} MCP</span></div></div>`).join(''); | |||
There was a problem hiding this comment.
a.id and a.version are interpolated into HTML without escapeHTML (the name on the same line is escaped). CSP limits impact, but please escape consistently.
| s=s.replace(/_([^_]+)_/g, '<em>$1</em>'); | ||
| s=s.replace(/~~([^~]+)~~/g, '<del>$1</del>'); | ||
| s=s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, url)=>{ | ||
| const cleanUrl=safeURL(url.trim()); |
There was a problem hiding this comment.
The text was already escaped earlier, so escaping the URL again turns & into &amp; and breaks links. Escape once.
LoopForge now ships two parts in one repository:
cli/): the existing workflow installed into a single coding agent. Unchanged for users:npx -y loopforge-cli@latest install <agent>and the npm release workflow keep working. MIT.Commits:
cli/(pure moves plus path and CI fixes).platform-v*) and CLI (v*) releases distinct names.docker composedeployment.Verified locally:
go vet,go test ./..., API e2e, Playwright (31 tests), CLIvalidate.sh, internal-reference and gitleaks scans, a compose deployment running concurrent runs in separate sandbox containers.Made with Cursor