feat(cli): route create/queue/jobs through the hub /api/v1 (thin CLI Step 8) - #292
feat(cli): route create/queue/jobs through the hub /api/v1 (thin CLI Step 8)#292madarco wants to merge 5 commits into
Conversation
…Step 8)
Every CLI create now goes through POST /api/v1/boxes + streams
GET /api/v1/jobs/:id/logs — no inline provider.create() in create.ts. The hub
keeps the canonical fork: a local workspace (projectId) -> file queue; a repoUrl
(or a projectId with no local folder) -> the control-plane clone queue. Same
202 {jobId} contract, same client, both modes.
Why:
- A hub-routed create (web UI and --via-hub) came up MISSING .env/untracked
files — only the direct cloud create pushed seed material. The seed push is now
unconditional on the create path (pushProjectSeedToCustody, hash-skipped), so
the clone-side worker overlays what a fresh clone can't provide.
- controlPlaneCreateRequest and the /remote/boxes POST handler silently DROPPED
agentArgs, so a hub-routed `claude -i` lost its processed args (skip-permissions
etc.). agentArgs now travels end-to-end (+ a regression test).
Highlights:
- create-target.ts picks WHICH HUB (reuses Step 1's preferLocal ladder); the
via-hub agent helpers (_cloud-agent-via-hub.ts) move onto client.createBox with
unchanged signatures, so claude/codex/opencode aren't edited.
- Foreground lane: an interactive `agentbox create` runs in its own scheduler
lane (selectNextRunnableForeground), UNGATED by queue.maxConcurrent — it must
never sit queued behind background -i jobs.
- CreateBoxInput/QueueJobCreateOpts widened to carry EVERY create flag (image,
snapshot, size/location/inbound, build, credentialSync, bundleDepth, useBranch,
gitPushMode, envFiles, carry, ...); _run-queued-job threads them into
provider.create — a dropped flag is an invisible capability regression.
- queue list / hub jobs read one unified GET /api/v1/jobs; job view gained
error/provider/name/agent/createdAt (a failed create reports a failure, not a
silent "done"). Claude login-code affordance preserved (job-stream.ts).
- Retired control-plane/{hub-enqueue,hub-jobs}.ts (route-create.ts kept for the
launchers, deferred to Step 11). --from-branch host-validation fixed
structurally (remote path passes the ref through; backend validates).
Verified e2e on a local hub (docker): `agentbox create -y` built the box through
the hub and a working-tree-only .env arrived in /workspace (ground truth). The
foreground lane started a create immediately while a background -i job stayed
queued behind a saturated maxConcurrent=1 gate. queue list / hub jobs render the
unified view. Unit tests cover the foreground selector, parseCreateBox
(repoUrl XOR projectId + opts), the agentArgs round-trip, the client, and
job-stream (failure-surfaces + login-code). git-LFS already handled (#249).
Claude-Session: https://claude.ai/code/session_013vVkccHaK73qdWmqaiHai6
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
… agentbox/step8-create # Conflicts: # apps/cli/src/commands/_cloud-agent-via-hub.ts
- queue list: print the FULL job id, not a 12-char slice — `queue show`/`cancel` resolve it against the local manifest by exact id (no prefix match), so a truncated id wouldn't resolve. (Bugbot High-adjacent Medium) - _cloud-agent-via-hub: require the EXACT box id from resolveBox before adopting; drop the `?? boxes[0]` fallback that could adopt + SSH-config the wrong box on an ambiguous/mismatched match set. (Bugbot High) - parseCreateBox: reject sending BOTH projectId and repoUrl — the backend forks on repoUrl vs projectId, so both is ambiguous (repoUrl would win and skip a valid local workspace). (Bugbot Medium) - submitLoginCode: a control-plane create job (visible to getJob via the Store, not readJob) now returns a distinct "no in-box login-code channel" message instead of a misleading "job not found". (Bugbot Medium) Claude-Session: https://claude.ai/code/session_013vVkccHaK73qdWmqaiHai6
|
bugbot run |
- relay queue scheduler: `queue.enabled=false` no longer blocks a FOREGROUND create. That flag bounds the background `-i` box lane only; the foreground lane (an interactive `agentbox create`, now enqueued through the hub) and the prepare lane run regardless — otherwise a disabled queue wedges an interactive create at `queued` until its stream times out. Guarded the background `while` on `cfg.enabled` instead of an early tick return; hygiene (sweep/recover) now runs always too. Unit-tested. (Bugbot High) - _run-queued-job: log the on-create resync-conflict warning BEFORE the no-agent early return (docker + cloud), so a plain `agentbox create` (noAgent) surfaces it in its streamed create log, matching the old inline path. (Bugbot Medium) - queue list: degrade to the local `~/.agentbox/queue/` manifests when the hub is stopped/unreachable, instead of failing — `/api/v1/jobs` stays the primary view (quiet, no autostart) but `queue list` works offline again. (Bugbot Medium) Claude-Session: https://claude.ai/code/session_013vVkccHaK73qdWmqaiHai6
|
bugbot run |
… path Bugbot High: a cloud create routed to a control box (`runCreateViaHubApi`) posted a minimal body and the control-plane worker never applied box-shaping flags, so `--snapshot`/`--image`/`--with-env`/`--build`/`--bundle-depth`/`--no-vnc`/ `--no-credential-sync` were silently ignored on the remote path (the local file-queue path already carried them). Now carried end-to-end: create.ts remote body -> parseCreateBox `opts` -> controlPlaneCreateRequest -> `CreateJobRequest.opts` -> the control-plane worker's `provider.create`. Applied as direct provider args (checkpointRef/image/withEnv/ withPlaywright/vnc/bundleDepth/allowPull/credentialSync). VM sizing (`--size`/`--location`/`--inbound`) is NOT carried — it needs the CLI's provider-specific sizing helper, unavailable worker-side, so it falls back to the control box's own config, consistent with how `prepare` uses the control box's config pins. Carry (host files) / portless / limits are docker/local-only and inapplicable to a control-box clone build, so they aren't sent either. Unit tests assert the opts round-trip through controlPlaneCreateRequest. Claude-Session: https://claude.ai/code/session_013vVkccHaK73qdWmqaiHai6
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d298d5c. Configure here.
| // On-create resync conflicts (checkpoint-restore path): prepend the warning to | ||
| // the queued prompt so the background agent opens on it. | ||
| const resyncWarning = result.resync ? buildResyncWarning(result.resync) : null; | ||
| if (resyncWarning) log.write(resyncWarning); |
There was a problem hiding this comment.
Remote host ignored in docker worker
High Severity
Foreground create now threads remoteHost in job opts, but runDockerJob still calls local createBox without remoteHost or providerForCreate. Jobs routed down the docker branch ignore the remote engine even when the submitter supplied a host.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d298d5c. Configure here.


Step 8 — create, queue and jobs: one path through
/api/v1Part of
docs/hub-api-single-path-plan.md. The biggest step: it collapses the CLI's three create wires (inlineprovider.create(), the local file queue, and the remote-only/remote/boxesadmin wire) into one client path —POST /api/v1/boxes→ streamGET /api/v1/jobs/:id/logs— against a local hub and a control box alike.What changed
createfully converts — no inlineprovider.create()increate.ts. The hub keeps the canonical fork: a resolvable local workspace (projectId) → the file queue →_run-queued-job; arepoUrl(or aprojectIdwith no local folder) → the control-plane clone queue. Same202 {jobId}.create-target.tspicks WHICH HUB (reusing Step 1'spreferLocalladder /remoteHubConfigured/cloud.viaHub/--via-hub/--local)._cloud-agent-via-hub.ts) move ontoclient.createBox+ the shared streamer with unchanged signatures, soclaude.ts/codex.ts/opencode.tsare untouched (their local foreground = create+attach is the IO plane, out of scope).queue list/hub jobsread one unifiedGET /api/v1/jobs.control-plane/{hub-enqueue,hub-jobs}.ts.route-create.tsis kept (the launchers still importresolveCreateRouting; its deletion is deferred to Step 11 with the launcher conversion).Two defects this fixes (both found while here)
.env/untracked files. A hub-routed create (web UI and--via-hub) came up without files a fresh clone can't provide, because only the direct cloud create pushed seed material. The seed push is now unconditional on the create path (pushProjectSeedToCustody, hash-skipped so an unchanged tree costs nothing) — the clone-side worker overlays it.agentArgssilently dropped.controlPlaneCreateRequest(and the/remote/boxesPOST handler) discardedagentArgs, so a hub-routedclaude -ilost its processed args (e.g.--dangerously-skip-permissions). It now travels end-to-end, with a regression test.Foreground lane (hard requirement)
An interactive
agentbox createmust never sitqueuedbehind background-ijobs —queue.maxConcurrentbounds background fan-out, not interactive commands. A third scheduler lane (selectNextRunnableForeground, peer to the prepare lane) starts foreground creates ungated; the box still counts in the running total once live, it's just never blocked.create.tssetsforeground: true.No dropped flag
createis the most flag-heavy command.CreateBoxInput/QueueJobCreateOptswere widened to carry every create flag —image,snapshot,size/location/inbound,build,credentialSync,bundleDepth,useBranch,gitPushMode,envFiles,carry, … — and_run-queued-jobthreads them intoprovider.create/createBox(preferringcreateOptsover config for cloud sizing, which it dropped before). The job view gainederror/provider/name/agent/createdAtso a failed create reports a failure, not a silent "done". The Claude login-code affordance is preserved (job-stream.ts).How it was verified (e2e, ground truth)
agentbox create -y -n smokebuilt the box through the hub; a.envpresent only in the working tree arrived in/workspace(content-matched viadocker exec, not an exit code).queue.maxConcurrent=1and the gate saturated (1 running box + aqueuedbackground-ijob), a foregroundcreatestarted immediately and ran todonewhile the background job stayedqueued(verified via the queue manifests +queue list).queue list/queue list --all/hub jobs listall render off/api/v1/jobs.parseCreateBox(repoUrl XOR projectId + opts), theagentArgsround-trip, the new client methods, andjob-stream(failure-surfaces-as-failure + login-code round-trip).pnpm typecheck+pnpm lintclean; relay/hub/cli suites green.Env caveat (not a code gap): a genuine
hub exposeis blocked in-box by theghshim (no real GitHub token) — the same documented blocker as Steps 6/9/13. The docker-create path is mode-independent (docker always routes local viapreferLocal), so the local-hub run is the docker behavior in both modes; the remote-mode client path is the identicalwithHubClientwrapper Steps 6 & 9 already validated. git-LFS is already handled bycloneRepoWithLfs(#249) — verified present, unchanged.https://claude.ai/code/session_013vVkccHaK73qdWmqaiHai6
Note
High Risk
Large behavioral change to the primary
createpath, remote adoption, and queue scheduling; regressions could affect box provisioning, credentials/seeding, and background job concurrency.Overview
Unifies box creation and job inspection on
POST /api/v1/boxesplusstreamJobToCompletion(log SSE + polled job status), replacing inlineprovider.create(), admin/remote/boxes, andhub-enqueue/pollHubJob.agentbox createenqueues viaHubApiClient(projectIdlocally,repoUrlon a control box);resolveCreateTarget/pushCreateSeedpick the hub and push custody seed before remote clones so.env/untracked files aren’t lost.queue listandhub jobsshareGET /api/v1/jobs; interactive creates use aforegroundqueue lane so they aren’t blocked byqueue.maxConcurrent. Create bodies and workers now thread fullopts(image, snapshot, sizing,carry,envFiles,agentArgs, etc.); adoption resolves boxes viaGET /api/v1/boxes?ref=instead of the admin store.hub-enqueue.tsandhub-jobs.tsare removed;route-create.tsremains for launcher commands until a later step.Reviewed by Cursor Bugbot for commit d298d5c. Configure here.