Skip to content

feat(cli): route create/queue/jobs through the hub /api/v1 (thin CLI Step 8) - #292

Open
madarco wants to merge 5 commits into
feat/cli_api_consolidationfrom
agentbox/step8-create
Open

feat(cli): route create/queue/jobs through the hub /api/v1 (thin CLI Step 8)#292
madarco wants to merge 5 commits into
feat/cli_api_consolidationfrom
agentbox/step8-create

Conversation

@madarco

@madarco madarco commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Step 8 — create, queue and jobs: one path through /api/v1

Part of docs/hub-api-single-path-plan.md. The biggest step: it collapses the CLI's three create wires (inline provider.create(), the local file queue, and the remote-only /remote/boxes admin wire) into one client path — POST /api/v1/boxes → stream GET /api/v1/jobs/:id/logs — against a local hub and a control box alike.

What changed

  • create fully converts — no inline provider.create() in create.ts. The hub keeps the canonical fork: a resolvable local workspace (projectId) → the file queue → _run-queued-job; a repoUrl (or a projectId with no local folder) → the control-plane clone queue. Same 202 {jobId}. create-target.ts picks WHICH HUB (reusing Step 1's preferLocal ladder / remoteHubConfigured / cloud.viaHub / --via-hub / --local).
  • The launchers' via-hub helpers (_cloud-agent-via-hub.ts) move onto client.createBox + the shared streamer with unchanged signatures, so claude.ts/codex.ts/opencode.ts are untouched (their local foreground = create+attach is the IO plane, out of scope).
  • queue list / hub jobs read one unified GET /api/v1/jobs.
  • Retired control-plane/{hub-enqueue,hub-jobs}.ts. route-create.ts is kept (the launchers still import resolveCreateRouting; its deletion is deferred to Step 11 with the launcher conversion).

Two defects this fixes (both found while here)

  • Missing .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.
  • agentArgs silently dropped. controlPlaneCreateRequest (and the /remote/boxes POST handler) discarded agentArgs, so a hub-routed claude -i lost 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 create must never sit queued behind background -i jobs — queue.maxConcurrent bounds 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.ts sets foreground: true.

No dropped flag

create is the most flag-heavy command. CreateBoxInput/QueueJobCreateOpts were widened to carry every create flag — image, snapshot, size/location/inbound, build, credentialSync, bundleDepth, useBranch, gitPushMode, envFiles, carry, … — and _run-queued-job threads them into provider.create/createBox (preferring createOpts over config for cloud sizing, which it dropped before). The job view gained error/provider/name/agent/createdAt so 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)

  • Local hub (docker): agentbox create -y -n smoke built the box through the hub; a .env present only in the working tree arrived in /workspace (content-matched via docker exec, not an exit code).
  • Foreground lane: with queue.maxConcurrent=1 and the gate saturated (1 running box + a queued background -i job), a foreground create started immediately and ran to done while the background job stayed queued (verified via the queue manifests + queue list).
  • Unified view: queue list / queue list --all / hub jobs list all render off /api/v1/jobs.
  • Unit tests: the foreground selector + gate-skip, parseCreateBox (repoUrl XOR projectId + opts), the agentArgs round-trip, the new client methods, and job-stream (failure-surfaces-as-failure + login-code round-trip).
  • pnpm typecheck + pnpm lint clean; relay/hub/cli suites green.

Env caveat (not a code gap): a genuine hub expose is blocked in-box by the gh shim (no real GitHub token) — the same documented blocker as Steps 6/9/13. The docker-create path is mode-independent (docker always routes local via preferLocal), so the local-hub run is the docker behavior in both modes; the remote-mode client path is the identical withHubClient wrapper Steps 6 & 9 already validated. git-LFS is already handled by cloneRepoWithLfs (#249) — verified present, unchanged.

https://claude.ai/code/session_013vVkccHaK73qdWmqaiHai6


Note

High Risk
Large behavioral change to the primary create path, 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/boxes plus streamJobToCompletion (log SSE + polled job status), replacing inline provider.create(), admin /remote/boxes, and hub-enqueue/pollHubJob. agentbox create enqueues via HubApiClient (projectId locally, repoUrl on a control box); resolveCreateTarget / pushCreateSeed pick the hub and push custody seed before remote clones so .env/untracked files aren’t lost.

queue list and hub jobs share GET /api/v1/jobs; interactive creates use a foreground queue lane so they aren’t blocked by queue.maxConcurrent. Create bodies and workers now thread full opts (image, snapshot, sizing, carry, envFiles, agentArgs, etc.); adoption resolves boxes via GET /api/v1/boxes?ref= instead of the admin store. hub-enqueue.ts and hub-jobs.ts are removed; route-create.ts remains for launcher commands until a later step.

Reviewed by Cursor Bugbot for commit d298d5c. Configure here.

…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
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agentbox-web Skipped Skipped Jul 30, 2026 5:29am

Request Review

Comment thread apps/cli/src/commands/queue.ts
Comment thread apps/cli/src/commands/_cloud-agent-via-hub.ts Outdated
Comment thread apps/hub/app/(dashboard)/api/v1/lib/validate.ts
Comment thread apps/hub/lib/hub-backend.ts
… 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
@madarco

madarco commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

bugbot run

Comment thread packages/relay/src/queue.ts
Comment thread apps/cli/src/commands/create.ts
Comment thread apps/cli/src/commands/queue.ts Outdated
- 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
@madarco

madarco commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

bugbot run

Comment thread apps/cli/src/commands/create.ts
… 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
@madarco

madarco commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

bugbot run

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d298d5c. Configure here.

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