Skip to content

Fix(plane)/fix issue: control plane - #1089

Closed
ironjing wants to merge 7 commits into
boxlite-ai:mainfrom
ironjing:fix(plane)/iron_control_plane
Closed

Fix(plane)/fix issue: control plane#1089
ironjing wants to merge 7 commits into
boxlite-ai:mainfrom
ironjing:fix(plane)/iron_control_plane

Conversation

@ironjing

@ironjing ironjing commented Jul 29, 2026

Copy link
Copy Markdown

Initial Demo

Summary

  1. fix issue: Control plane: make remote foreground boxlite run reliable #1043
  2. the initial framework code
  3. Adds the control-plane foundation for reliable remote foreground runs by persisting launch configuration and delaying the main process until the client can attach.
    为可靠的远程前台运行补充控制面基础能力:持久化主进程启动配置,并延迟启动主进程,避免客户端完成连接前,快速命令已经执行结束。

Changes

  1. Persist entrypoint, command, working directory, TTY, detach mode, and cleanup intent with each Box.
  2. Validate and map foreground launch options at the REST boundary.
  3. Propagate launch options and skipStart through Runner API V0 and V2.
  4. Keep foreground Boxes STOPPED after creation to prevent the state reconciler from starting them before attachment.
  5. Apply command, working-directory, and detach options in the Go Runner; log unsupported create-time TTY requests.
  6. Add focused coverage for launch mapping, validation, and synchronous/asynchronous state handling.
    持久化 entrypoint、命令参数、工作目录、TTY、detach 模式和退出后清理意图。
    在 REST 接口入口校验并转换前台启动参数。
    通过 Runner API V0 和 V2 传递启动配置及 skipStart。
    前台 Box 创建完成后保持 STOPPED,同时将 desiredState 设置为 STOPPED,防止状态协调器提前启动主进程。
    Go Runner 已支持命令参数、工作目录和 detach 配置;创建时的 TTY 暂不支持,仅记录警告日志。
    增加启动参数映射、校验,以及同步和异步状态处理测试。

Risks / rollout

  1. The main-process attach proxy is not yet implemented end to end, so this is foundational rather than a complete foreground-run solution.
  2. autoDeleteAfterExit records --rm intent, but exit-triggered deletion is not yet implemented.
  3. Create-time TTY is not supported by the current Go SDK and currently emits a warning.
  4. New launch fields do not yet have a capability or minimum-version guard. Apply the schema migration first, then deploy Runner before API.
    主进程 attach 代理尚未端到端实现,因此这次修改只是远程前台运行的基础能力,还不是完整解决方案。
    autoDeleteAfterExit 目前只记录 --rm 意图,主进程退出后自动删除 Box 的逻辑尚未实现。
    当前 Go SDK 不支持创建时设置 TTY,tty: true 只会产生警告日志。
    新增启动字段暂时没有 Runner capability 或最低版本检查。部署时应先执行数据库迁移,再升级 Runner,最后升级 API。

Summary by CodeRabbit

New Features

  • Added configurable box launch settings, including command, entrypoint, working directory, TTY, detach, and automatic cleanup after exit.
  • Launch settings are now saved and included when viewing boxes.
  • Foreground launches can prepare a box without immediately starting its main process.
  • Non-detached box creation now returns promptly after creation.

Bug Fixes

  • Improved foreground launch state handling to prevent unintended automatic restarts.

Tests

  • Expanded coverage for launch configuration validation, mapping, and lifecycle behavior.

@ironjing
ironjing requested a review from a team as a code owner July 29, 2026 14:36
@boxlite-agent

boxlite-agent Bot commented Jul 29, 2026

Copy link
Copy Markdown

📦 BoxLite review — couldn't complete

claude exited 1

stdout:
{"is_error":true,"duration_api_ms":0,"num_turns":1,"stop_reason":"stop_sequence","session_id":"ae8ef04f-304e-44e0-ac12-2a90509e6abc","total_cost_usd":0,"usage":{"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{},"permission_denials":[],"terminal_reason":"api_error","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subtype":"success","api_error_status":403,"result":"Your organization has disabled Claude subscription access for Claude Code · Use an Anthropic API key instead, or ask your admin to enable access","type":"result","duration_ms":303,"uuid":"ecbb5ec9-9aa2-4bfc-8e40-07498f04bf10"}

stderr:
<empty>

powered by BoxLite

@cla-assistant

cla-assistant Bot commented Jul 29, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fc6d5ce-82ec-4eac-9b9b-53591ec1dc16

📥 Commits

Reviewing files that changed from the base of the PR and between e5c4020 and 78339ce.

📒 Files selected for processing (15)
  • apps/api/src/box/dto/box.dto.ts
  • apps/api/src/box/dto/create-box.dto.ts
  • apps/api/src/box/entities/box.entity.ts
  • apps/api/src/box/managers/box-actions/box-start.action.ts
  • apps/api/src/box/managers/box-actions/box.action.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v0.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v2.ts
  • apps/api/src/box/services/box.service.ts
  • apps/api/src/box/services/job-state-handler.service.ts
  • apps/api/src/boxlite-rest/boxlite-box.controller.ts
  • apps/api/src/boxlite-rest/dto/create-box.dto.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts
  • apps/runner/pkg/api/dto/box.go
  • apps/runner/pkg/boxlite/client.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • apps/api/src/boxlite-rest/boxlite-box.controller.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts
  • apps/api/src/box/dto/create-box.dto.ts
  • apps/api/src/box/managers/box-actions/box.action.ts
  • apps/api/src/box/entities/box.entity.ts
  • apps/api/src/box/dto/box.dto.ts
  • apps/api/src/box/managers/box-actions/box-start.action.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.ts
  • apps/api/src/box/services/job-state-handler.service.ts
  • apps/api/src/box/services/box.service.ts
  • apps/api/src/boxlite-rest/dto/create-box.dto.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v2.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v0.ts
  • apps/runner/pkg/boxlite/client.go
  • apps/runner/pkg/api/dto/box.go

📝 Walkthrough

Walkthrough

Launch configuration is added to box creation contracts, persisted with boxes, forwarded through V0 and V2 runner paths, and applied during runtime creation. Foreground launches defer process startup and settle in STOPPED.

Changes

Foreground launch configuration

Layer / File(s) Summary
Launch contracts and request mapping
apps/api/src/box/entities/box.entity.ts, apps/api/src/box/dto/*, apps/api/src/boxlite-rest/dto/*, apps/api/src/boxlite-rest/mappers/*, apps/libs/runner-api-client/src/models/create-box-dto.ts, apps/runner/pkg/api/dto/box.go, apps/api/src/boxlite-rest/boxlite-box.controller.ts
Launch fields are validated, mapped into launchConfig, and represented in API and runner DTOs.
Launch configuration persistence
apps/api/src/box/services/box.service.ts, apps/api/src/box/dto/box.dto.ts
Box creation stores launchConfig, and API responses include it.
Runner launch forwarding
apps/api/src/box/runner-adapter/*, apps/runner/pkg/boxlite/client.go
Runner creation APIs forward launch fields and skipStart. The Go client maps supported fields to SDK options and warns when create-time TTY is requested.
Foreground creation lifecycle
apps/api/src/box/managers/box-actions/*, apps/api/src/box/services/job-state-handler.service.ts, apps/api/src/box/services/*spec.ts
Foreground creation skips startup and sets the box state and desired state to STOPPED after successful creation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • boxlite-ai/boxlite#755 — Modifies the box creation, boot, and runner creation paths used by this change.
  • boxlite-ai/boxlite#1091 — Modifies startup job completion and box-state reconciliation used with the updated completion handling.

Suggested reviewers: dorianzheng

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant BoxliteBoxController
  participant BoxService
  participant RunnerAdapterV2
  participant JobStateHandlerService
  Client->>BoxliteBoxController: Create box with launch configuration
  BoxliteBoxController->>BoxService: Create box with launchConfig
  BoxService->>RunnerAdapterV2: createBox(box, metadata, skipStart)
  RunnerAdapterV2->>JobStateHandlerService: CREATE_BOX job with launch fields
  JobStateHandlerService->>BoxService: Set state and desiredState to STOPPED
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the summary, changes, and risks, but it omits the required call graph and verification steps. Add Before and After call-graph sections with changed hops, and add commands or steps that reviewers can use to verify the changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title identifies the control-plane fix, which matches the PR’s main objective, although it does not mention foreground-run support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@apps/api/src/box/managers/box-actions/box.action.ts`:
- Around line 67-69: The desired-state update currently allows notifications
before the database transaction commits. In
apps/api/src/box/managers/box-actions/box.action.ts lines 67-69, prevent
extraUpdateData from bypassing the service-level post-commit transition flow; in
apps/api/src/box/managers/box-actions/box-start.action.ts lines 87-96 and
apps/api/src/box/services/job-state-handler.service.ts lines 113-116, route
foreground creation and V2 create-job completion through that same flow. Ensure
BoxEvents.DESIRED_STATE_UPDATED is emitted only after the surrounding
transaction successfully commits.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v2.ts`:
- Line 150: Update RunnerAdapterV2.inferStateFromJob so completed CREATE_BOX
jobs map to STOPPED when job.getPayload().skipStart is true, while preserving
the existing STARTED result otherwise. Add a regression test covering a
foreground CREATE_BOX job and the subsequent inferred state.

In `@apps/api/src/box/services/box.service.ts`:
- Around line 258-259: Update BoxService.create so image-matching warm-pool
boxes are reused only when createBoxDto.launchConfig is absent. Ensure requests
with launchConfig continue through normal creation, preserving launchConfig
persistence and requested entrypoint, command, working directory, detach mode,
and foreground status; leave assignWarmPoolBox unchanged unless it is extended
to apply the complete configuration.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 703a814a-daa7-49d9-ab69-3c95320b011c

📥 Commits

Reviewing files that changed from the base of the PR and between 053e381 and 5603516.

⛔ Files ignored due to path filters (1)
  • apps/go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (20)
  • apps/api/src/box/dto/box.dto.ts
  • apps/api/src/box/dto/create-box.dto.ts
  • apps/api/src/box/entities/box.entity.ts
  • apps/api/src/box/managers/box-actions/box-start.action.spec.ts
  • apps/api/src/box/managers/box-actions/box-start.action.ts
  • apps/api/src/box/managers/box-actions/box.action.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v0.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v2.ts
  • apps/api/src/box/services/box.service.ts
  • apps/api/src/box/services/job-state-handler.service.spec.ts
  • apps/api/src/box/services/job-state-handler.service.ts
  • apps/api/src/boxlite-rest/boxlite-box.controller.ts
  • apps/api/src/boxlite-rest/dto/create-box.dto.spec.ts
  • apps/api/src/boxlite-rest/dto/create-box.dto.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts
  • apps/libs/runner-api-client/src/models/create-box-dto.ts
  • apps/runner/pkg/api/dto/box.go
  • apps/runner/pkg/boxlite/client.go

Comment on lines 67 to +69
const updateData: Partial<Box> = {
// 状态转换有时需要原子地修正 desiredState 等关联字段。
...extraUpdateData,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Publish foreground desired-state transitions only after commit.

These paths persist desiredState: STOPPED through BoxRepository.update. Route the atomic state/desired-state transition through a service-level post-commit flow (or transactional outbox), then notify proxies only after the write commits; otherwise a proxy can act on an uncommitted or rolled-back desired state.

  • apps/api/src/box/managers/box-actions/box.action.ts#L67-L69: prevent generic extra fields from bypassing the post-commit desired-state notification flow.
  • apps/api/src/box/managers/box-actions/box-start.action.ts#L87-L96: use the post-commit flow for V0 foreground creation.
  • apps/api/src/box/services/job-state-handler.service.ts#L113-L116: use the same post-commit flow for V2 create-job completion.

Based on learnings, BoxEvents.DESIRED_STATE_UPDATED side effects must be emitted only after the surrounding transaction commits.

📍 Affects 3 files
  • apps/api/src/box/managers/box-actions/box.action.ts#L67-L69 (this comment)
  • apps/api/src/box/managers/box-actions/box-start.action.ts#L87-L96
  • apps/api/src/box/services/job-state-handler.service.ts#L113-L116
🤖 Prompt for 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.

In `@apps/api/src/box/managers/box-actions/box.action.ts` around lines 67 - 69,
The desired-state update currently allows notifications before the database
transaction commits. In apps/api/src/box/managers/box-actions/box.action.ts
lines 67-69, prevent extraUpdateData from bypassing the service-level
post-commit transition flow; in
apps/api/src/box/managers/box-actions/box-start.action.ts lines 87-96 and
apps/api/src/box/services/job-state-handler.service.ts lines 113-116, route
foreground creation and V2 create-job completion through that same flow. Ensure
BoxEvents.DESIRED_STATE_UPDATED is emitted only after the surrounding
transaction successfully commits.

Source: Learnings

networkAllowList: box.networkAllowList,
metadata,
authToken: box.authToken,
skipStart,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve skipStart when inferring V2 box state.

Line 150 forwards the flag, but RunnerAdapterV2.inferStateFromJob still maps every completed CREATE_BOX job to STARTED. After a foreground job completes, job-state-handler.service.ts stores STOPPED; a later boxInfo() call can report STARTED again. Read job.getPayload().skipStart in the CREATE_BOX state mapping and add a regression test.

Suggested fix
-      case JobType.CREATE_BOX:
-        return job.status === JobStatus.COMPLETED ? BoxState.STARTED : BoxState.CREATING
+      case JobType.CREATE_BOX: {
+        if (job.status !== JobStatus.COMPLETED) return BoxState.CREATING
+        const skippedStart = job.getPayload<{ skipStart?: boolean }>()?.skipStart === true
+        return skippedStart ? BoxState.STOPPED : BoxState.STARTED
+      }
🤖 Prompt for 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.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v2.ts` at line 150, Update
RunnerAdapterV2.inferStateFromJob so completed CREATE_BOX jobs map to STOPPED
when job.getPayload().skipStart is true, while preserving the existing STARTED
result otherwise. Add a regression test covering a foreground CREATE_BOX job and
the subsequent inferred state.

Comment on lines +258 to +259
// 启动参数必须随 Box 落库,不能只存在于首次发往 Runner 的请求中。
box.launchConfig = createBoxDto.launchConfig

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not reuse warm-pool boxes when launch configuration is present.

When an image-matching warm-pool box exists, create() returns through assignWarmPoolBox() before reaching these lines. That path neither persists launchConfig nor applies the requested entrypoint, command, working directory, or detach mode; it also emits the reused box as STARTED, breaking foreground creation.

Gate warm-pool reuse on the absence of launch configuration, or implement a complete reconfiguration path.

Proposed fix
-      } else if (image) {
+      } else if (image && !createBoxDto.launchConfig) {

Based on the supplied BoxService.create and assignWarmPoolBox flow:

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 启动参数必须随 Box 落库,不能只存在于首次发往 Runner 的请求中。
box.launchConfig = createBoxDto.launchConfig
} else if (image && !createBoxDto.launchConfig) {
🤖 Prompt for 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.

In `@apps/api/src/box/services/box.service.ts` around lines 258 - 259, Update
BoxService.create so image-matching warm-pool boxes are reused only when
createBoxDto.launchConfig is absent. Ensure requests with launchConfig continue
through normal creation, preserving launchConfig persistence and requested
entrypoint, command, working directory, detach mode, and foreground status;
leave assignWarmPoolBox unchanged unless it is extended to apply the complete
configuration.

@boxlite-agent boxlite-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📦 BoxLite review — 2 issues

undefined,
undefined,
undefined,
{ desiredState: BoxDesiredState.STOPPED },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 BoxDesiredState used but never imported
Line 95 references BoxDesiredState.STOPPED but the file's imports (lines 7-19) never import BoxDesiredState, so apps/api fails to compile/typecheck; verified via grep '^import' showing no such import.

Suggested change
{ desiredState: BoxDesiredState.STOPPED },
import { BoxDesiredState } from '../../enums/box-desired-state.enum'

grep '^import' box-start.action.ts — BoxDesiredState used at line 95 but never imported

@boxlite-agent boxlite-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📦 BoxLite review — 2 issues

Comment on lines +56 to +63
createDto.launchConfig = {
entrypoint: dto.entrypoint,
cmd: dto.cmd,
workingDir: dto.working_dir,
tty: dto.tty,
detach: dto.detach,
// detach=false 表示 CLI 需要像本地运行一样持续接收主进程输出和退出码。
foreground: dto.detach === false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ autoDeleteAfterExit persisted but unused
createDto.launchConfig.autoDeleteAfterExit is computed from auto_delete for foreground runs but no consumer of BoxLaunchConfig.autoDeleteAfterExit exists anywhere in the diff or repo (grep found none), so run --rm-style foreground deletion is silently a no-op despite being modeled as supported.

@ironjing ironjing closed this Aug 28, 2026
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