Skip to content

fix(sop): provide a default sandbox and validate sandbox selection - #176

Merged
Robert0622 merged 2 commits into
mainfrom
fix/sop-sandbox-selection
Nov 11, 2026
Merged

Robert0622 merged 2 commits into
mainfrom
fix/sop-sandbox-selection

Conversation

@Robert0622

@Robert0622 Robert0622 commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

A fresh database could serve the UI without any sandbox catalog rows. Creating a blank SOP then submitted the hardcoded sandboxProfileId: "local" and failed with sandbox profile not found. Reproduced this using the published 0.3.4 image and a real browser: plain Compose startup produced an empty sandbox/template catalog and HTTP 400; running the complete installer against the same database restored the seeded profiles and made the unchanged UI succeed.

The platform now provides a built-in default local sandbox when that database row is absent. Both catalog listing and lookup expose it without writing seed data. Persisted operator configuration takes priority; database failures or malformed stored configuration do not trigger fallback. The default cannot be deleted. Compose's existing AGENTFLOW_LOCAL_SANDBOX=docker policy still executes it in a separate Docker container; native deployment policy is unchanged. Project-scoped asset queries now include globally shared profiles while continuing to exclude private foreign/global assets.

The SOP wizard also selects from actual project/shared/global profiles, preserves valid template settings, requires an explicit replacement for an unavailable regular-template profile, and keeps generic-template adoption on its existing clone endpoint. Defensive empty/stale-catalog guidance and refresh preserve the entered draft fields.

Scope

  • Platform core / API / runner (catalog default and project-scoped asset listing)
  • Public adapter / deployment (platform web UI and deployment documentation)
  • Classic
  • Portable
  • Shared behavior contract
  • Installer / CLI
  • Documentation only

Compatibility

  • No API shape, schema, migration or startup seed writes. Existing local profiles retain their names, scope, driver and environment; the built-in fallback only applies when the physical row is absent. Other missing profile IDs still fail validation.
  • local is reserved as the non-deletable default; operators can edit its persisted configuration or select another sandbox. Runtime Docker isolation and configured non-local drivers are preserved.
  • The explicit installer still imports starter SOPs and the welcome project. Those catalog templates are not synthesized by the application.
  • Rebased onto main after fix(deploy): update Go security patch and install missing prerequisites #179 merged; the Go security and installer changes are already upstream and are not duplicated in this PR.
  • The reporter's deployment history is unavailable. Missing initialization is a verified reproduction path, not a confirmed account of their exact installation.

Validation

  • Fresh MySQL 8.4, no initialization SQL, real Chromium: bootstrap and project-scoped sandbox listing expose the default; the wizard automatically selects it; blank SOP creation returns HTTP 200. Added a real script and human approval to that SOP, verified the output file inside its separate Docker container, approved it, and observed successful completion. Sandboxes, Dockerfiles and generic-template tables remain empty before and after this test.
  • Repository regressions cover zero-row default availability without writes, stored configuration priority, no duplicate default, deletion protection, unknown IDs, malformed configuration and database failures. API regressions cover bootstrap, project-scoped visibility, private-asset exclusion and actual SOP creation.
  • Six wizard scenarios and the flow-editor regression tests passed, covering custom selection, unavailable profiles, refresh preserving input, template preservation and generic-template clone/fallback behavior.
  • Full non-browser Go suite: passed.
  • The four isolated real-Chromium Go acceptance tests: passed.
  • go vet ./..., go test ./web -count=1 -timeout=180s, frontend syntax check, python3 scripts/check-public.py, bash scripts/scan-secrets.sh and git diff --check: passed.
  • Classic/Portable suites, model-backed tasks and native-host execution were not run; their implementations and native execution policy are unchanged. CI is rerun on the updated head.

Safety

  • No credentials, internal endpoints, production data or deployment-specific infrastructure were included.
  • User files and unrelated changes were preserved.
  • Generated Classic host bundles are not applicable; Classic is unchanged.
  • No third-party source or assets were added.

@Robert0622

Copy link
Copy Markdown
Collaborator Author

LoopForge PR Review Bot

  • Head SHA: e2e7cc59dc0b7e7719fff7ff9e500aa8e3f61d7d
  • Conclusion: ✅ Pass

Checks performed (isolated worktree):

  • npm --prefix chatbot ci && npm run build → succeeded (35.51s)
  • go test ./web/... → ok agentflow/web 13.082s, covering the new webtest/tests/sop-sandbox-selection.test.mjs scenarios (no-local-sandbox fallback to a project sandbox, missing-sandbox blocks submit and preserves the form, template sandbox retained / requires explicit replace when missing, generic-template retained-sandbox via clone endpoint, generic-template fallback to server-side default)
  • go build ./... → succeeded

Code check: the new sopSandboxChoices() / defaultSOPSandbox() / syncSOPSandboxSelection() / refreshSOPSandboxes() in web/app/flows.js correctly reuse the existing catalogVisible/catalogOwned helpers already defined in web/app.js (lines ~697-705) — no missing-reference risk.

No blocking issues found. Removing the hardcoded sandboxProfileId:'local' in newWorkflow() / createSOPFromWizard() in favor of selecting from the available sandbox list matches the stated goal (avoid missing local sandbox when creating SOPs).

@cocofhu cocofhu mentioned this pull request Nov 11, 2026
2 tasks done
@Robert0622
Robert0622 force-pushed the fix/sop-sandbox-selection branch from e2e7cc5 to cf7cd01 Compare November 11, 2026 11:23
@Robert0622 Robert0622 changed the title fix(web): avoid missing local sandbox when creating SOPs fix(sop): provide a default sandbox and validate sandbox selection Nov 11, 2026
@Robert0622

Copy link
Copy Markdown
Collaborator Author

LoopForge PR Review — head cf7cd01ec582389179f3b5e8731a5ed24936cee6

Verdict: ✅ Pass

  • Re-review: head moved from e2e7cc59dc0b7e7719fff7ff9e500aa8e3f61d7d (previously Pass) to cf7cd01ec582389179f3b5e8731a5ed24936cee6. This is a substantive increment, not a rebase: adds the actual default-sandbox mechanism (internal/repo/sandbox_defaults.go, CatalogRepo.Sandbox/Sandboxes/DeleteSandbox guard) plus the SOP-wizard sandbox picker (web/app/flows.js, new #sopCreateSandbox control).
  • Checked out cf7cd01 in an isolated sandbox (no secrets/no prod access), built Go 1.26.11 + chatbot (Node 22), and read the full diff against base a529e25.
  • CatalogRepo.Sandbox("local") now falls back to a read-only, non-seeding DefaultSandboxProfile() only when the row is absent; a stored local row still wins untouched; corrupt stored JSON and DB failures correctly do not fall back (verified by TestDefaultSandboxDoesNotMaskCorruptConfigurationOrDatabaseFailure). DeleteSandbox("local") is explicitly rejected.
  • filterEnvironmentAssets relaxed from owner==projectID || (owner!="" && shared) to owner==projectID || shared, so the new ProjectID-less default is visible to every project. Checked all existing Shared:true seed rows across sandboxes/dockerfiles/environments/agents/rules/skills — every one already carries ProjectID:"platform", so this doesn't newly expose any pre-existing global asset; only the new empty-owner default benefits from the relaxed check.
  • Test evidence (ran locally, isolated sandbox, no secrets):
    • go test ./internal/repo/... ./internal/httpapi/... → all pass, including the 3 new sandbox_defaults_test.go cases and TestProjectEnvironmentAssetsIncludeSharedGlobalDefaults / TestDefaultSandboxWithoutCatalogRowsSupportsSOPCreation.
    • node --test webtest/tests/sop-sandbox-selection.test.mjs → 6/6 pass (blank-SOP fallback, missing-sandbox block + refresh, template preservation, generic-template clone fallback, direct newWorkflow() draft).
  • No blocking issues found.

@fx-kurumi

Copy link
Copy Markdown
Collaborator

PR #176 review — head cf7cd01ec582389179f3b5e8731a5ed24936cee6

No blocking issues. UI verified on a live build of this head. The earlier bot Pass covered e2e7cc59; this review covers cf7cd01, which adds the in-memory local fallback.

Live UI verification (synthetic data, throwaway MariaDB)

  1. Stored local row: #/sops → 新建 SOP → 选择起点. The sandbox select preselects Local, the hint shows, and creation lands in #/flow/<id>. See the recording.
  2. No local row (row removed in the DB): GET /api/sandboxes returns local / 默认运行沙箱 / driver=local / shared=true. The wizard preselects 默认运行沙箱 and creation succeeds. No rows were written. See the recording.
  3. Delete: DELETE /api/sandboxes?id=local returns 409 内置沙箱 local 不能删除, the same as main.
Stored local — step 2 No row (fallback) — step 2 Fallback — summary
stored fallback summary

No page errors.

Non-blocking design note

  • Where: internal/repo/sandbox_defaults.go:15-16 and internal/repo/catalog.go:339. The fallback is Shared: true with Driver: local.
  • Concern: outside Compose (AGENTFLOW_LOCAL_SANDBOX unset), local executes on the platform host. An installation that never imported migrations/seed/001_public_catalog.sql previously had no host-execution sandbox. After this change it exposes one to every project. The seed row (本地执行(仅可信任务)) is also not marked shared.
  • Suggestion: consider giving the fallback a name that says host execution / trusted only (like the seed row), or not sharing it outside Compose. The behaviour is documented in docs/deployment.md, so I am not blocking on it.

Executed checks

  • go test ./internal/repo -run DefaultSandbox, targeted ./internal/httpapi sandbox tests: ok.
  • node --test webtest/tests/sop-sandbox-selection.test.mjs: 6/6.

@fx-kurumi

Copy link
Copy Markdown
Collaborator

✅ 结论:Approve(通过)

审查 head cf7cd01(Bot 之前审的是 e2e7cc59,当时只有前端向导的改动;当前 head 新增了后端的内置默认沙箱提交,本次一并独立审查)。

结论:未发现阻塞问题,可以合入。

后端

  • CatalogRepo.Sandbox(internal/repo/catalog.go:333)只在 ErrRecordNotFound 且 id 为 local 时返回内置默认值;数据库出错或 payload 损坏时仍返回 not found,不会悄悄换成本地执行。Sandboxes() 的兜底也走 Sandbox(),规则相同;表里已有 local 行时以该行为准,不会重复列出。
  • 是否违反 AGENTS.md「代码不做 seed」:没有写任何行,也没有新增 ensureXxx 之类的启动自愈逻辑,读路径上的只读兜底与 BuiltInExecutors() 是同一类做法。按字面不违反规则,但这是在代码里提供默认数据的新先例,建议维护者明确认可。
  • 权限:filterEnvironmentAssets(internal/httpapi/catalog.go:858)现在会列出 ProjectID=="" 且 shared 的全局资产。私有的全局资产和其他项目的私有资产仍然被排除(TestProjectEnvironmentAssetsIncludeSharedGlobalDefaults)。写权限没有放宽:assetOwner(project_write_access.go:132)把 ProjectID=="" 的兜底 local 视为全局资产,只有管理员能 PUT,普通项目成员无法抢占或改写 local。
  • 删除保护:handler 层原本就拒绝删除 local(catalog.go:681),repo 层新加的拒绝属于双重保护,行为一致。

前端向导

  • sopSandboxChoices 复用了 catalogVisible,选择优先级为 local → 本项目 docker → 任意 docker → 第一个。模板原有沙箱不可用时置空并禁用提交;通用模板仍走 clone 端点;「刷新沙箱」不会清空已填的表单内容;HTML 插值都用了 escapeHTML。没有发现问题。

非阻塞建议

  1. 兜底的 local 名为「默认运行沙箱」、shared:true,而 migrations/seed/001_public_catalog.sql 里的 local 行名为「本地执行(仅可信任务)」且没有 shared。由此导致装没装 seed 行为不一致:

    • 没装 seed 时,共享 SOP 可以用 local,/api/sandboxes?projectId= 也会列出它;
    • 装了 seed 时,这两点都不成立。

    另外,非 Compose 的原生部署没有设置 AGENTFLOW_LOCAL_SANDBOX=docker,此时 local 直接在宿主机执行,叫「沙箱」容易误导。建议名称沿用 seed 中的「本地执行(仅可信任务)」,并考虑 shared 是否与 seed 对齐。

  2. docs/deployment.md 可以补一句:原生部署下默认 local 直接在宿主机执行。

验证

  • go vet ./internal/repo ./internal/httpapi:通过。
  • go test -p 4 -count=1 ./internal/repo:ok。
  • go test -p 4 -count=1 ./internal/httpapi(先用 scripts/build-chatbot.sh 生成 embed 资源):新增的 TestDefaultSandboxWithoutCatalogRowsSupportsSOPCreation 和 TestProjectEnvironmentAssetsIncludeSharedGlobalDefaults 通过。只有 TestMainBotBrowserStartsAndDrivesRunBrowser 和 TestBrowserStreamPushesFramesAndRunsCommands 失败;这两个用例在 main 上同样失败,是本环境缺浏览器 sidecar 导致的,与本 PR 无关。
  • internal/engine(本 PR 未改动):有 2 个 git 身份相关用例失败,在 main 上同样失败,属于环境问题。
  • webtest:node --test tests/sop-sandbox-selection.test.mjs tests/flows-editor.test.mjs tests/hui-controls-and-sops.test.mjs tests/flow-designer.test.mjs 共 15/15 通过。
  • 端到端:用一次性 MariaDB 空库(不导入任何 seed)启动 PR 构建的平台(关闭鉴权,仅本地访问)。
    • /api/sandboxes 和 ?projectId=demo 都只返回内置的 local。
    • 在 Chromium 中打开新建 SOP 向导,选择「从空白开始」,会自动选中「默认运行沙箱」(见截图)。
    • 提交后 SOP 保存成功,sandboxProfileId=local,sandboxes 表仍为 0 行。

@crispinhu27

Copy link
Copy Markdown
Collaborator

LGTM

@Robert0622
Robert0622 merged commit e83d9c8 into main Nov 11, 2026
16 checks passed
@Robert0622
Robert0622 deleted the fix/sop-sandbox-selection branch November 11, 2026 14:29
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.

3 participants