Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Connector: make the local agent a durable, installable machine (`chopin` CLI, persisted pairing, service, reconnect)

Cerrado
#523 1 comentario 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@lavaman131 ya está trabajando en esto.

Desde el 12/10/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

Problem

The connector in apps/connector works well while someone keeps a terminal open, but it isn't yet something a team can leave running. We (@lavaman131) plan to implement the steps below and want to agree on the shape first.

The current gaps:

  • It needs Bun and curl. Setup downloads a script with curl … /connector.js and runs it with bun 1.4.2 (docs/local-launcher.md, docs/local-experiments.md). After a deploy, the fix is to "rerun the whole command", because that downloads the connector again. Users get no installed executable, no version check, and nothing a service can point at.
  • Pairing doesn't survive a restart or sign-out. "The connector credential is held in memory and tied to your browser login … Reconnect after a server restart or logout." The cause is in the server code. Connections keeps pending pairings, connections and tokens in in-memory Maps (apps/server/src/experiments/connections.ts:50-59). Each Connection stores the browser sessionId (connections.ts:11). revokeSession drops a connection when that browser session ends (connections.ts:228-230), and routes.ts:343 resolves the browser session again on every connector call. As a result, every deploy needs a person back at the terminal to pair again. The Let Chopin build and prototype automatically on this laptop choice (autoRun) is also lost and asked again.
  • The connector doesn't reconnect. In main.ts, the wait_for_work loop rethrows any error that isn't an abort (main.ts:135-136), so a network blip or server restart ends the process. During a run, a failed heartbeat aborts the agent (run.ts:80: .catch(() => abort.abort())). The server forgets a connection after 90 seconds of silence (CONNECTION_MS, connections.ts:38) and "fails its builds and rebuilds and interrupts its investigations immediately" when the connector disconnects.
  • Nothing installs or starts it. The docs say so: "Keep the connector running. Each browser Build action starts an agent process; automatically installing or starting the connector itself remains future work." (docs/local-launcher.md:35-36, repeated at line 98-100 as "automatic connector startup … remain future work").
  • One process serves one checkout. "One connector process serves one checkout" (docs/local-experiments.md, Trouble connecting). Each checkout has its own lock, created in lockWorkspace.
  • Users can't choose the machine. The server picks "your idle connection for the repository, preferring the one that last ran work for this document" (docs/local-launcher.md:51-53). The UI doesn't let anyone see or choose which machine a document or repository uses.
  • Planning and building need separate local processes. Under HARNESS=remote, the Planner runs in a client attached over /planner-link with a GitHub bearer (docs/planner-link.md). Builds and investigations run through the connector over /connector/mcp. The same laptop needs two separate long-lived processes with two different credentials. Attaching also needs "the same account … signed in to Chopin in a browser, on this server, since the server last started" (sign-in-required), so a server restart breaks the Planner side too.

#521 makes a missing agent visible ("Start your local agent"). This issue is about keeping the agent from going missing.

Plan

Each step is meant to be one reviewable PR. Steps run in order unless a step says otherwise. The interactive connect flow and GET /connector.js keep working throughout.

  1. Ship the connector as an installable chopin CLI that needs no Bun or curl.

    • Compile apps/connector/src/main.ts with bun build --compile for linux-x64, linux-arm64, darwin-x64, darwin-arm64 and windows-x64.
    • Publish it on npm as one package per platform behind a small launcher package that picks the right one through optionalDependencies (the esbuild pattern). That way npm install -g <package> works with plain Node and no Bun.
    • Attach the same binaries to GitHub releases, with install.sh and install.ps1 that install to a stable per-user path.
    • Make the stdio bridge work from a compiled binary. Today it is spawned as process.execPath [resolve(process.argv[1]), "bridge"] (run.ts:151-161, main.ts:128). In a compiled binary it has to be [process.execPath, "bridge"].
    • Have the CLI send its version when it pairs or connects. On an incompatible version, the server refuses with a message naming the upgrade command, replacing "rerun the whole command".
    • Keep /connector.js served and documented as a fallback during the migration.
    • The service step depends on this step, because a service unit must point at a stable installed executable rather than bun ~/.chopin/connector.js.
  2. Persist a revocable machine credential.

    • chopin connect [checkout] -- <ACP command> pairs once through the existing link and confirmation code. It stores a machine credential in the state directory (stateDirectory(), mode 0600) and stores the ACP command and checkout in a machine config.
    • On the server, keep the machine (owner, label, repositories, autoRun, credential fingerprint) in storage instead of the in-memory Connections maps, and stop tying it to the browser sessionId. Reconnecting after a server restart, a deploy or a browser sign-out then needs no new pairing.
    • Add revocation in two places: a Disconnect machine action in the UI and chopin disconnect on the machine. A revoked credential gets a distinct error code, and the CLI stops instead of retrying.
    • The per-call push-access recheck stays, but it must work without a live browser session (see Open questions).
  3. Reconnect with backoff.

    • Wrap the wait_for_work loop in a reconnect loop. Retry transient failures (network errors, 5xx, connection-unavailable after a restart) after 1, 2, 5, 10, 20 and 30 seconds, then every minute.
    • Stop only on revocation, lost repository access or an incompatible version.
    • A failed heartbeat during a run no longer aborts the agent right away. Reporting is held and retried until the server's grace period for the run expires.
  4. Server-side liveness and drop handling.

    • Measure liveness on the server side, as /planner-link now does with pings every 15 seconds and a 120-second timeout. Either keep the long poll and track last-heard per machine, or move the machine channel to a socket with pings. Then a connector killed without warning is noticed promptly, and a connector that is still alive isn't dropped.
    • When a machine drops, its runs move to a "machine offline" state within a grace period instead of failing at once. When the same machine reconnects, it reports which runs it still holds.
    • A reconnect with the same machine credential replaces that machine's own stale connection immediately. It must not count toward the 8-connection limit (connections.ts:95) or wait out CONNECTION_MS.
  5. Several checkouts per machine, and chopin status.

    • One chopin process serves every checkout in its config, keeping one lockWorkspace lock per checkout. chopin connect in another checkout adds that checkout to the running machine instead of starting a second process.
    • chopin status prints:
      • the installed version and the version the running process uses;
      • the server and the paired account;
      • whether the credential is stored (never the credential itself);
      • each checkout and repository, with its connection state and last contact;
      • the current runs with their worktree paths;
      • the log path.
  6. chopin service install|status|uninstall.

    • Linux: a systemd user unit with Restart=always, enabled with systemctl --user enable --now, plus loginctl enable-linger so the machine runs at boot and after logout. When enabling linger needs an administrator, print the one sudo command and install nothing.
    • macOS: a LaunchAgent with RunAtLoad and KeepAlive, loaded with launchctl bootstrap gui/<uid>.
    • Windows: a per-user Task Scheduler task that restarts after failure. On Windows, stopping must ask the running process to stop through a file or pipe, because Task Scheduler kills the process with no signal.
    • The unit runs the absolute path of the installed chopin executable, and output is appended to a log under the state directory.
    • service status reports running, stopped, not-installed, or "restart pending" when the installed version differs from the running one.
    • uninstall stops the service gracefully and leaves the credential, config and worktrees in place.
  7. Choose a machine per repository or per document in the UI.

    • List the owner's machines: label, last seen, version, repositories and the autoRun choice.
    • Allow pinning a machine to a repository, with an optional override per document. Build, Run, rebuilds and spikes go to the pinned machine. If none is pinned, the current preference applies (the machine that last ran work for this document, then the owner's most recently active idle one).
    • Another person's machine is never eligible, as today.
  8. Launch a Planner backend for HARNESS=remote documents from the same machine.

    • The machine config can name a Planner backend command next to the ACP command.
    • For each document assigned to the machine, the machine creates a separate worktree, starts that command there and attaches it over /planner-link. When the link drops, it attaches again with the same backoff.
    • One paired machine then serves both planning and builds.
    • A stale link left by this machine must not block its own reattach. Today a dead link holds the document as planner-attached, refusing the same account, until the 120-second timeout and then the 2-minute grace period. The server should let the same machine replace its own dead link. At minimum, the client must recognise itself as the holder and retry quickly rather than wait minutes.
    • This step depends on step 2's credential decision for sign-in-required.

Requirements carried into each step

  • Outbound only. The machine never listens on a public interface. The loopback bridge in local-bridge.ts stays loopback.

  • A drop pauses work and never answers. When a link or connection drops, the machine pauses its runs and keeps their worktrees. Open questions, Decisions and ACP permission requests are withdrawn and are never answered on anyone's behalf. Runs resume only through an explicit resume or a person's action after reattach. A service has no TTY, so permission requests stay declined as today (run.ts:181) and are never auto-approved by the machine.

  • Pairing survives restarts. A credential tied to a browser sign-in breaks on every deploy, so the machine credential must outlive server restarts, deploys and browser sign-out. It must still be revocable from both the UI and the CLI. It is stored at 0600 and never printed, logged or written into a unit file or plist.

  • The service PATH survives a reboot. The unit records the absolute chopin path and a PATH where the agent command and git still resolve after a reboot:

    • the directory of the resolved agent executable comes first;
    • entries under $XDG_RUNTIME_DIR or /run/user/<uid> are dropped;
    • per-shell version-manager directories (for example fnm's fnm_multishells) are dropped.

    service install checks that the ACP command resolves under that PATH before it writes anything. On Windows, the task inherits the user's environment variables.

  • A stale link from the same machine must not block reattach. This applies to connector connections and /planner-link alike, and to the local .lock file. staleLock already replaces a lock whose process is gone.

  • Worktrees stay isolated. Each run keeps its own worktree under runs/<id> (prepareWorkspace), and each Planner document gets its own worktree. Local edits are preserved. Spikes keep noPushEnvironment(). stopProcessesIn still runs when a job ends.

  • No regressions. Every step keeps bun run e2e e2e/implementation-launcher.e2e.ts e2e/connector.e2e.ts passing and adds tests for the behaviour it introduces: a restart without re-pairing, reconnect after a drop, revocation, and reattach past a stale link.

Open questions

  1. Planner link vs ACP. Should the machine keep two channels (/connector/mcp for builds and investigations, /planner-link for the Planner), or should Planner turns move onto the machine's connector channel under one credential? With two channels, does /planner-link accept the machine credential in place of a GitHub bearer?

  2. Repository credential for a durable machine. Repository tools and push-access rechecks currently use the browser login's GitHub App token (routes.ts:343, and sign-in-required on /planner-link). The options we see:

    • store a GitHub App user refresh token server-side for each machine;
    • run a device-flow sign-in on the machine;
    • persist browser sessions across restarts.

    Which does the team prefer?

  3. Packaging. Is bun build --compile acceptable given the binary size? What should the npm package name and scope be? Should the binaries be code-signed and notarized for macOS and Windows? Should the CLI update itself, or only tell the user when the server needs a newer version?

  4. Running builds during a drop. Should a build keep running locally through a short drop and report on reconnect, or pause at once?

  5. Permission prompts under a service. Should they stay declined, rely on the agent's own auto-approval flag after --, or be routed to the owner in Chopin?

  6. Ownership. Who owns the CLI, its release pipeline and the service units after they land? Should they live in apps/connector or in a new apps/cli?

Lenguaje dominante
TypeScript
Estrellas
392
Forks
22
Merge medio
15 h 9 min
PR fusionados (30 d)
338

Preparar el entorno

  • Incluye un Dockerfile o un archivo de Docker Compose
  • Sin plantilla de pull request
  • Sin guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de githubnext/chopin

Todos los issues de githubnext/chopin

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.