Skip to content

Vercel provider backlog: 9 items done + plans for the rest#22

Merged
madarco merged 38 commits into
mainfrom
agentbox/agentbox-dce55776
May 29, 2026
Merged

Vercel provider backlog: 9 items done + plans for the rest#22
madarco merged 38 commits into
mainfrom
agentbox/agentbox-dce55776

Conversation

@madarco
Copy link
Copy Markdown
Owner

@madarco madarco commented May 29, 2026

Summary

Nine Vercel-provider backlog items — each investigated, implemented, live smoke-tested on Vercel, and committed:

Plus a docs commit with host-actionable plans for the 4 remaining heavier/interactive items: #4 relay round-trip, #8 ttyd attach, #14 per-project snapshot tier, #16 Sandbox.fork.

Test plan

  • pnpm typecheck && pnpm lint && pnpm test green (592 tests; +11 new unit tests: buildExposedPorts, parseNetworkPolicy)
  • Live on Vercel (base snapshot snap_jxQPP…): vercelVcpus=4sandbox.vcpus===4; VNC binds 6080 + vnc.html 200; expose 3000 → public domain(3000) 200; deny-all blocks egress + allowlist permits only listed domains; builder-cleanup snapshot survival; full prepare bake
  • --provider vercel from the published (non-monorepo) CLI bundle — staging done, end-to-end run pending

Note: this branch is based on fork-104454; if that was squash-merged into main, the PR range may include already-merged commits — rebase onto main if so.


Note

Medium Risk
New cloud backend touches credentials, box lifecycle, checkpoints, relay polling, and published CLI assets; several paths were live-tested but full host relay git round-trip from a Vercel box remains unverified in automation.

Overview
Adds a fourth sandbox backend, Vercel Sandbox (--provider vercel), as @agentbox/sandbox-vercel: Firecracker microVMs on AL2023, one-time agentbox prepare --provider vercel base snapshot, SDK exec/upload/preview URLs (no SSH, no nested Docker). Attach uses a staged attach-helper.js tmux bridge over the SDK; checkpoints store Vercel snapshot ids in the shared cloud-checkpoint manifest.

Wires the provider through the CLI (registry, vercel subcommand/login, argv sugar), relay resolveCloudBackend, and shared cloud scaffolding: optional launchDockerd: false, plus create-time exposePorts, timeoutMs, and networkPolicy (used by Vercel for vCPU/timeout/egress). Config gains box.defaultCheckpointVercel, box.vercelVcpus, box.vercelTimeoutMs, box.vercelNetworkPolicy. checkpoint and prune are generalized over daytona/hetzner/vercel; stage-runtime.mjs stages runtime/vercel/ (including attach-helper chunks) for the published CLI.

Also: agentbox.yaml carry: for Vercel CLI OAuth store paths; VNC noVNC web-root detection for AL2023; destroy avoids deleting the shared base snapshot when currentSnapshotId aliases the source; Hetzner prepared-state drops the unused projects map (docs aligned). Extensive docs/vercel-* and cloud-provider updates; relay/git PATH fix on AL2023 is documented in the backlog (provision + profile shim).

Reviewed by Cursor Bugbot for commit dd48da9. Configure here.

madarco added 15 commits May 28, 2026 11:15
Adds `@agentbox/sandbox-vercel` (`--provider vercel`), a fourth backend
built on @vercel/sandbox (Firecracker microVMs + snapshots), composed via
the shared CloudBackend scaffolding like Daytona/Hetzner.

- Base env baked once via `agentbox prepare --provider vercel` (Vercel can't
  build from a Dockerfile): boot node24 → provision.sh → sandbox.snapshot().
- Persistent sandboxes give pause/resume for free (stop auto-snapshots,
  get({resume:true}) resumes); destroy purges the box's snapshot.
- Public HTTPS preview URLs via sandbox.domain(port); no in-box dockerd
  (no nested containers) and no SSH (custom tmux attach helper over the SDK).
- Checkpoints store the Vercel snapshot id in the cloud-checkpoint manifest.
- OIDC auth decodes the JWT's owner_id/project_id and passes explicit creds
  (the SDK's env-OIDC path needs Vercel-CLI linkage a box doesn't have).

Wires registry/argv-prefix/CLI, config (ProviderKind + defaultCheckpointVercel
+ schema), relay resolveCloudBackend, and a launchDockerd opt-out in the cloud
scaffold. Includes unit tests + docs (cloud-providers.md, vercel-backlog.md).
Enumerate the P0 live-smoke items (nothing run end-to-end against real
Vercel yet — both dev OIDC tokens were expired), P1 functional gaps
(VNC on AL2023, attach latency/ttyd, published-CLI asset staging, builder
cleanup, OIDC expiry, per-provider resource config), and P2 deferred parity
work (checkpoint list view, per-project snapshot tier, prune, fork, ports,
networkPolicy).
…shot status on prepare skip

Two bugs found during the first live e2e:

- `destroy` purged `currentSnapshotId`, which for a box that hasn't paused is
  still the snapshot it booted from — so destroying any box deleted the shared
  base snapshot (and broke every subsequent create with a 410). Guard the purge
  with `currentSnapshotId !== sourceSnapshotId` so a box's own auto-snapshot is
  still cleaned but the base / checkpoint source survives.
- `prepare`'s skip-fast treated "Snapshot.get didn't throw" as "exists", but the
  SDK resolves deleted/failed tombstones too (status field). Require
  status === 'created' so a deleted base triggers a real rebuild.

Verified live: prepare bakes the base, create boots from it (user=vscode,
/workspace on agentbox/<box>, claude/codex/opencode present, docker correctly
unavailable), public *.vercel.run URL resolves, and destroy preserves the base.
…udo works

Vercel's AL2023 base ships /etc/sudoers without an includedir for
/etc/sudoers.d (and with non-0440 perms), so provision.sh's NOPASSWD drop-in
for vscode was silently ignored: `sudo -n` as vscode failed with "a password
is required", which broke the cloud workspace seed ($SUDO rm/mkdir/chown) and
would break ctl-launch / carry too. provision.sh now appends the includedir,
normalises perms to 0440, and visudo-validates.

Surfaced while running the remaining P0 live e2e (pause/resume + checkpoint
round-trip), now validated 11/11 from the host against a re-baked base. Also:

- scripts/vercel-live-e2e.sh: harness for #5/#6 + the destroy/base regression.
  Reads live SDK status (agentbox list reports cloud boxes as optimistically
  running), moves the /workspace marker over `agentbox cp`, reads the snapshot
  id from the checkpoint manifest, uses --carry skip for non-TTY runs.
- packages/sandbox-vercel/test/live-state.mjs: live Vercel status helper.
- docs/vercel-backlog.md: #5/#6 marked validated; three live bugs documented.
- docs/vercel-sandbox-findings.md: platform issues written up for Vercel.
…ner + vercel

`agentbox checkpoint ls` showed only docker + daytona; hetzner and vercel
cloud checkpoints were invisible. Generalize the command over a CLOUD_BACKENDS
list so all four providers are handled uniformly:

- ls: merge hetzner + vercel snapshot rows (with the *default marker)
- set-default: accept --provider hetzner|vercel and resolve their stores
- rm: remove hetzner/vercel snapshots (symmetric with daytona) and sweep the
  box.defaultCheckpointVercel dangling pointer on delete

Closes backlog #13.
…ized pruneCloud

The orphan-sandbox prune was daytona-only. Generalize pruneDaytona into a
provider-agnostic pruneCloud over a CLOUD_PRUNE_PROVIDERS list so vercel and
hetzner (both previously unwired despite working backend.list()) enumerate
cloud sandboxes and offer to delete the ones missing from state.json.

Closes backlog #15.
The vercel runtime-assets resolver already looked under runtime/vercel/, but
stage-runtime.mjs never populated it, and buildVercelAttach only resolved
attach-helper.js from the monorepo dist — so --provider vercel worked only from
a monorepo checkout. Stage attach-helper.js + provision.sh + ctl/shims + baked
config into runtime/vercel/, and add the next-to-dist candidate to
resolveAttachHelperPath. Verified all 11 runtime assets resolve from the staged
tree with the monorepo fallback disabled.

Closes backlog #9.
…expiry)

OIDC dev tokens expire on a ~12h cycle and resolveCredentials has no headless
refresh, so a long `agentbox prepare --provider vercel` (or CI) can outlive the
token. Document the access-token trio as the recommended path for long/headless
jobs in cloud-providers.md, and surface the same guidance in the `vercel login`
prompt + option labels.

Closes backlog #11 (documentation half; auto-refresh stays unbuilt).
prepare left the builder for Vercel's reaper out of caution that delete might
cascade to the snapshot. Verified live that it doesn't: a snapshot stays
status:created (256MB) and boots a fresh sandbox after its source is deleted.
prepare.ts now deletes the builder (step 8) best-effort, after the snapshot id
is persisted, so a delete failure leaves at most a lingering sandbox, never a
broken bake.

Closes backlog #10.
…vercelTimeoutMs)

vcpus and session timeout were hardcoded (2 / 45min). Add flat config keys
box.vercelVcpus + box.vercelTimeoutMs (matching the box.defaultCheckpointVercel
convention) and thread them: config -> providerOptions (vercel-only) ->
cloud-provider resource/timeout overrides -> CloudProvisionRequest.timeoutMs +
resources.cpu -> Sandbox.create({ resources: { vcpus }, timeout }). daytona /
hetzner are untouched (they don't set the override keys). Region stays fixed
iad1 (Vercel constraint).

Verified live: vercelVcpus=4 yields sandbox.vcpus===4 (default 2).

Closes backlog #12.
agentbox-vnc-start hardcoded --web=/usr/share/novnc, but the AL2023 bake
git-clones noVNC to /usr/local/share/novnc. websockify does os.chdir(--web) at
startup, so the missing dir raised FileNotFoundError and it never bound 6080
(the "agentbox-vnc-start failed: websockify did not bind 6080" symptom).
Everything else (Xvnc, vncpasswd, websockify, noVNC assets) was present and
Xvnc bound 5901. Resolve the web dir from [/usr/share/novnc,
/usr/local/share/novnc] (Debian/Ubuntu first, so docker + hetzner are
unaffected). Verified live: 6080 binds, vnc.html HTTP 200, web root
/usr/local/share/novnc. The script is in the prepare fingerprint, so the next
prepare auto-rebakes.

Closes backlog #7.
…s route

The cloud scaffold minted a preview URL per services.*.expose.port, but on
Vercel a URL only routes to a port declared at Sandbox.create({ ports }), and
only [6080, 8788] were declared — so service URLs 404'd. Read the expose ports
before provision and thread them via CloudProvisionRequest.exposePorts; the
vercel backend's buildExposedPorts merges the non-privileged ones (<1024 is a
Vercel 400) onto the base set, capped at Vercel's 4-port limit. daytona/hetzner
ignore the field (one WebProxy routes all ports).

Verified live: expose 3000 -> ports [6080,8788,3000], and the public
domain(3000) URL returns 200 from an in-box server. Unit-tested buildExposedPorts
(privileged-drop, dedupe, 4-cap).

Closes backlog #17.
Surface Vercel's networkPolicy as a config-driven egress lock (the safety win
the backlog flagged, cf. the hetzner firewall). New box.vercelNetworkPolicy:
'allow-all' (default), 'deny-all', or a comma-separated domain allowlist.
Threaded config -> providerOptions (vercel-only) -> CloudProvisionRequest.networkPolicy
-> Sandbox.create({ networkPolicy }); parseNetworkPolicy maps the string to the
SDK shape. daytona/hetzner ignore it (hetzner locks egress via its own firewall).

Verified live: a deny-all box can't resolve example.com; an example.com-allowlist
box reaches example.com (200) but not api.github.com. Unit-tested parseNetworkPolicy.
extendTimeout deferred (niche; session length is set at create via vercelTimeoutMs).

Closes backlog #18 (networkPolicy half).
…tems

The 9 actionable items are done. Write concrete implementation plans (from this
session's investigation) into the backlog for the heavier/interactive remainder
so they can be picked up on the host:
- #4 relay round-trip: why nested doesn't work + the host runbook (E2E_RELAY=1)
- #8 ttyd attach: bake ttyd + 4th port + ws client rewrite (needs a re-bake)
- #14 per-project snapshot tier: prepared-state projects[<hash>] mirroring daytona/hetzner
- #16 Sandbox.fork: SDK is ready; needs a finalizeCloudBox refactor of the shared
  create() + an `agentbox fork` command + a branch-semantics decision
Comment thread packages/sandbox-vercel/src/backend.ts Outdated
Comment thread packages/relay/src/host-actions.ts
madarco added 2 commits May 29, 2026 06:47
…ection)

Two issues Cursor Bugbot flagged on the vercel provider:

1. provision.sh installed the relay-routing git shim at /usr/local/bin/git
   *before* the VNC step's `git clone` of noVNC. The shim intercepts `clone`
   (no real-git fallback) and routes to the host relay, which doesn't exist
   during the bake. It happened to work only because bash had already hashed
   `git`->/usr/bin/git at the earlier `git config` step — fragile. Move the
   gh+git shim install dead-last (after every git/gh use, before the /tmp trim).

2. buildRunCommand shell-quoted env *values* but interpolated env *keys* raw
   into a `bash -lc` string that runs as root — a key like `x;rm -rf /` would
   inject. Reject keys that aren't POSIX env-var names.

Unit-tested the injection guard (valid value still quoted; bad key throws).
- backend.ts: call parseNetworkPolicy(req.networkPolicy) once into a local
  instead of twice in the conditional spread (truthiness gate + value could
  diverge if it ever gained side effects).
- host-actions.ts: dedupe resolveCloudBackend's triplicated try-catch into a
  shared loadCloudBackend helper. Kept the per-provider literal
  `'@agentbox/sandbox-'+'<name>'` import specifiers (not `+ name`) so esbuild
  still constant-folds + inlines each package via noExternal — a runtime
  variable specifier would MODULE_NOT_FOUND in the published CLI.
@madarco madarco marked this pull request as draft May 29, 2026 06:55
madarco added 10 commits May 29, 2026 06:56
…el AL2023

Found while verifying the git-shim-ordering fix: in a booted vercel box
`command -v git` resolves to /opt/git/bin/git (Vercel prepends /opt/git/bin
ahead of /usr/local/bin), so the relay-routing shims at /usr/local/bin are
inert at runtime — agent-initiated `git push`/`gh pr` hit the real binaries
instead of routing through the relay. Document the finding + a host fix plan
(prepend /usr/local/bin in the login-shell profile.d shim). Tied to #4.
Sandbox.fork in @vercel/sandbox@2.0.1 just reads the source's last
existing snapshot and creates from it -- no fresh snapshot, no memory,
no live filesystem read. On Vercel (cold, stop-only snapshots) that is
strictly weaker than the existing checkpoint create + create --snapshot
path. Mark #16 won't-build with the evidence and a revisit condition
(provider ships live/memory snapshots).
…mant hetzner projects tier

The per-project snapshot tier already exists: it's the `checkpoint create
--set-default` + `box.defaultCheckpoint<Provider>` flow (per-project via
hashProjectPath), and auto-capture at end of setup is driven cross-provider by
the /agentbox-setup skill (`agentbox-ctl checkpoint --set-default`). The
`projects[<hash>]` map #14 proposed would duplicate the checkpoint store, and
the Hetzner `projects` field it was modelled on was never wired.

- vercel-backlog #14 rewritten as Closed/redundant
- removed dormant PreparedProjectSnapshot type + projects field (+ export, test)
- reconciled hetzner-backlog deferred notes (answers the inline docker question)
- custom-system-CLAUDE.md: clarify vercel snapshots STOP the box; checkpoint
  only at end of setup, ask for confirmation, else rely on idempotent yaml tasks
- env-loader: read Vercel creds only from ~/.agentbox/secrets.env (drop
  .env.local harvesting); matches the daytona/hetzner model. Project
  .env/.env.local belong to the app, not the host CLI.
- credentials/cli: token-trio is the primary path (saved to secrets.env);
  OIDC must be in the shell env or secrets.env. Updated prompts + status msg.
- stage-runtime: stage attach-helper.js's transitive ./chunk-<hash>.js into
  runtime/vercel/ (hash changes each build, so walk the import graph). Without
  it 'agentbox shell' on a vercel box died ERR_MODULE_NOT_FOUND.
- docs: cloud-providers + vercel-backlog reflect secrets.env-only auth.
The old Phase D trusted the 'agentbox shell ... git push' exit code, but on
vercel 'shell' goes through the laggy attach pump whose exit code reflects the
attach wrapper, not the in-box command — yielding a false PASS on 2026-05-29
(commit never reached origin). Phase D now gates on ground truth: poll origin
for the box branch 'agentbox/<box>' appearing after the push (it's absent
before). Doc: revert #4 to unconfirmed and record the false-positive analysis.
A third, default-recommended auth mode for the Vercel provider that drives
the official 'sandbox'/'sbx' CLI through its browser OAuth, then reuses the
CLI's own credentials for our @vercel/sandbox SDK calls — no token to paste,
and no 12h OIDC expiry friction.

- cli-store.ts: platform-aware reader for the Vercel CLI store (auth.json /
  config.json); the live vca_ access token is read straight from it on every
  call (never copied to secrets.env), so the CLI is the self-refreshing source
  of truth. Only VERCEL_AUTH_SOURCE=cli + team/project ids are cached.
- sbx-cli.ts: detect/install/login/refresh driver (mirrors portless.ts).
- vercel-rest.ts: minimal fetch wrappers (projects + user) for project pick.
- sdk.ts: CLI branch in resolveCredentials() + ensureFreshCredentials(), which
  triggers the CLI's lazy token refresh (via 'sbx list') when near expiry, with
  in-process single-flight. Called at the top of every backend op + prepare +
  attach-helper.
- credentials.ts: third default 'cli' select option, runCliLogin() (install
  prompt -> browser login -> project pick-from-list -> persist), shared
  writeManaged(), CLI-aware status.

PAT-trio and OIDC modes are unchanged, so headless/CI is unaffected.
Live-verified end-to-end 2026-05-29 (login -> prepare -> create -> destroy).
The CLI-login auth mode keeps the OAuth token in the Vercel CLI's own store,
not in secrets.env (which only carries the VERCEL_AUTH_SOURCE=cli marker +
team/project). So the in-box agentbox couldn't use --provider vercel. Add carry
entries for the CLI store (auth.json + config.json), mapping the macOS host path
(and a Linux host candidate) onto the box's ~/.local/share/com.vercel.cli/ so the
in-box CLI-mode resolver reads the live token. optional:true everywhere, so
PAT/OIDC users (and hosts without the store) are unaffected.
@madarco madarco marked this pull request as ready for review May 29, 2026 13:06
@madarco
Copy link
Copy Markdown
Owner Author

madarco commented May 29, 2026

bugbot run

madarco added 2 commits May 29, 2026 14:09
Verified end-to-end on box relayv1: in-box agentbox-ctl git push of commit
fc6d54de reached origin (git ls-remote confirmed) via the bridge/poller/runGitRpc
chain. Root cause of prior failures was a stale host-relay process lacking the
vercel executor, plus the secrets.env/chunk/PATH fixes already committed. Records
the stale-relay gotcha + ensureRelay follow-up.
The Phase 7 `vercel login --status` rendering for CLI-login mode (async
printStatus + s.auth==='cli' branch + relativeExpiry + detectSbx probe) was
dropped from 392d38a by a concurrent worktree revert; restore it.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit dd48da9. Configure here.

madarco added 9 commits May 29, 2026 14:12
- Fix the recorded probe commit in #4 to the real pushed hash (7f8eea5d, was a
  wrong earlier-run value).
- Add backlog #21: ensureRelay should reclaim a relay lacking a provider
  executor (the stale-relay gotcha that masked #4), with a /healthz build-stamp
  plan. Cross-provider.
- vercel-rest.test.ts: non-null assertions on fetchMock.mock.calls[0] so
  tsc --noEmit (the CI typecheck step) passes under strict null checks.
- credentials.test.ts: clear VERCEL_AUTH_SOURCE in setup so the CLI-login
  marker (set by cli-auth.test.ts in the same worker, or present in the dev
  machine's secrets.env) can't leak into the trio/none cases.
… the wizard

findProjectRoot walks up to the first ancestor agentbox.yaml, so running the
wizard fixture in place resolves to the monorepo root: the wizard never fires
and the box inherits the root carry/tasks. Document copying the fixture to a
temp dir, and fix the smoke-test commands (absolute CLI path) accordingly.
uploadCarryPaths ran its mkdir/tar/chmod/chown one-liner via backend.exec with
no user, so Vercel's exec took the default path and wrapped it in
`sudo -u vscode -H bash -lc '<cmd>'`. That extra bash -lc nesting mangled the
command's $(...)/$var/while parent-chain-chown — $parent expanded empty,
`dirname "."` looped forever, and the exec hung (120s/attempt → ~425s with
retries, surfacing as 'Stream ended before command finished').

Run the extract as root (user:'root'), which is what the command was already
written for (the parent-chain chown comment says 'mkdir -p ran as root') and
keeps Vercel on its single bash -lc path. Hetzner/Daytona ignore opts.user
(they already run as their default/root user), so this only changes Vercel.

Verified live: the exact chown chain hangs 425s as vscode, runs in ~1s exit 0
as root. Added a regression assertion that carry execs with user:'root'.
Per review: don't rely on Hetzner/Daytona ignoring exec opts.user. Gate the
user:'root' on backend.name === 'vercel' so the other providers keep their
existing (working) carry path unchanged even if they later honor user:. Vercel
needs root because its non-root exec wraps in sudo -u vscode -H bash -lc, whose
nesting hangs this command. Files still end up vscode-owned (the command chowns
to uid 1000), so the in-box vscode user can write them. Tests cover both the
vercel (user:root) and non-vercel (unset) paths.
Vercel interactive attach (shell/claude/codex/opencode) was a laggy
send-keys/capture-pane poll bridge (attach-helper.ts) because the bundled
@vercel/sandbox SDK has no stdin/PTY channel. Replace it with the official
`sandbox` CLI's real PTY.

- build-attach.ts: emit `sbx exec --sudo [-i] --project <p> --scope <team>
  <name> -- sudo -u vscode -H bash -lc '<inner>'`. -i only for interactive
  shell/agent; detached pre-start + logs run non-interactively (live stream).
  <inner> reuses the shared cloud renderInnerCommand (now exported) so the tmux
  ensure + footer-aware config + `exec tmux attach` match hetzner/daytona.
- Token passed via child env VERCEL_AUTH_TOKEN (not argv → no ps leak): added
  AttachSpec.env, threaded through wrapped-pty/run.ts (ptySpawn + fallback) and
  the spawn sites (_cloud-attach runWrappedAttach + runDetached, shell, logs).
- Default sbx exec user is vercel-sandbox, so --sudo + `sudo -u vscode -H`
  runs tmux as the box user in /workspace (passed directly as argv, no outer
  bash -lc, avoiding a double-reparse).
- Deleted attach-helper.ts + its tsup entry + the stage-runtime chunk staging.
- credentials.ts: ensureSbxInstalled() now runs for every login mode so attach
  has the CLI; buildVercelAttach throws an actionable error if it's missing.

PoC + live e2e validated (default user, live streaming, env-token auth,
tmux-as-vscode, detached pre-start creates a reattachable session). Hetzner/
Daytona attach untouched (no AttachSpec.env). Interactive typing/resize/detach
is the remaining manual TTY check.
… as _)

sbx exec forwards neither TERM nor the locale (unlike ssh -t), so the box
attach session landed in TERM=unknown + an ASCII POSIX locale (charmap
ANSI_X3.4-1968). tmux then collapsed Claude Code's Unicode glyphs (logo,
braille spinner, box-drawing) to '_'. Prepend
`export LANG=C.UTF-8 LC_ALL=C.UTF-8 TERM=xterm-256color` to the attach inner
so the tmux server + the agent render UTF-8. Verified live: charmap becomes
UTF-8 and ─ ▲ ⠋ ✔ render correctly.
…us shows

The cloud create flow registered the box with the host relay BEFORE allocating
its per-project index, so the relay recorded projectIndex=undefined and wrote
status.json to the legacy no-index path (<id>-<mnemonic>/). But the saved box
record got projectIndex=N, so the host reader (agentbox list / wrapped-pty
footer / readBoxStatus) derived the <id>-<N>-<mnemonic>/ path and found nothing
-> the AGENT column was always empty for cloud boxes even while claude ran.

Allocate projectIndex first and pass it into registerBoxWithRelay so the relay
keys status.json on the same path the reader uses. Verified live: a fresh
vercel box now writes status.json to <id>-1-<name>/ matching its record.

(Existing cloud boxes self-heal on the next start/resume — that re-register
already passes projectIndex.)
… protocols

The leader/Actions footer only matched a raw 0x01 byte. TUIs like Claude Code
can switch the terminal into the kitty keyboard protocol or xterm
modifyOtherKeys, where Ctrl+a (and plain letters) arrive as escape sequences
(ESC[97;5u / ESC[27;5;97~) instead of raw bytes — so the leader silently
stopped working under those modes. Teach the input router to decode those
encodings for both the leader (Ctrl+a) and the chord keys, while staying
precise so cursor/mouse/function CSI sequences still forward untouched.

Tests cover kitty + modifyOtherKeys leader, kitty-encoded chords, mixed
raw-leader/kitty-chord, double-Ctrl+a literal, and the negative cases (cursor
keys and unmodified kitty keys are NOT treated as the leader).
@madarco madarco merged commit f58ef00 into main May 29, 2026
1 check passed
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