@diologue/local-agent
v0.9.0
Published
Diologue Coding Agent — local helper that pairs the cloud web UI with opencode running against a git repo on your machine. Binds to 127.0.0.1 only.
Downloads
2,112
Readme
@diologue/local-agent
Local helper that bridges the Diologue Coding Agent web UI to an OpenCode process running against a user-selected git repo on the same machine.
Quickstart (recommended)
Visit /coding-agent/quickstart on your Diologue deployment in a
browser. It will issue a one-time setup token and hand you a single
command to paste in a terminal:
npx @diologue/local-agent --cloud=<your-cloud> --token=<setup-token>The helper starts, calls back to the cloud, the browser tab redirects itself, and the coding agent is ready. No env vars, no copy-paste of URLs or tokens into the UI.
Standalone / power-user mode
# Run the helper directly, get a random pairing token to paste in
diologue-local-agent start --origin=https://your-app.comThis is v1 of a hybrid architecture. It binds to 127.0.0.1 only and
is intended to be reached by the browser running on the same machine.
A future version will open an authenticated tunnel back to the cloud so
the same UI can drive a remote machine — the HTTP surface is designed to
be compatible with that evolution.
Architecture
┌──────────────────┐ HTTPS ┌──────────────────┐
│ Diologue web UI │ ──────────────▶ │ Diologue cloud │ Existing app:
│ (your browser) │ ◀────────────── │ (Express + LLMs) │ auth, billing,
└──────────────────┘ └──────────────────┘ session storage
│ ▲
│ http://127.0.0.1:4099 │ llm_request brokered
│ (this helper) │ back through the
▼ │ browser → cloud LLM
┌──────────────────┐ @opencode-ai/sdk │ proxy → usage_ledger
│ Local Agent │ ─────────────────────────┘
│ (this package) │
└──────────────────┘
│ spawns/talks to
▼
┌──────────────────┐
│ opencode binary │ Installed separately — see Prerequisites.
│ on your machine │
└──────────────────┘Prerequisites
Node 20+ (matches the parent app).
OpenCode binary on
PATH. The official installer:curl -fsSL https://opencode.ai/install | bashVerify:
opencode --version.You can run the helper without opencode by forcing the mock adapter (see "Run" below) — useful for demos.
A provider key for opencode — opencode reads its own config (env vars /
~/.config/opencode/...). Today the helper uses opencode's native provider config for the actual model calls; the cloud LLM proxy + usage ledger are wired but not yet plumbed through opencode itself (see "Roadmap").
Install
# For end users — no clone required
npx @diologue/local-agent --help
# For development on this repo
cd local-agent && npm installThe published npm package is a single-file bundle produced by
npm run build. End users never need to clone the repo.
Run
cd local-agent
npm run dev # tsx watch — auto-reloads on edits
# or
npm startThe helper listens on http://127.0.0.1:4099 and prints a one-time
pairing token to the console. Paste that token into the Coding Agent
page in the web UI to connect.
[local-agent] Listening on http://127.0.0.1:4099
[local-agent] Adapter: opencode
[local-agent] Pairing token (paste into the web UI):
[local-agent] <random-token-here>
[local-agent] Allowed browser origin: http://localhost:5000
[local-agent] Press Ctrl+C to stop. The token is regenerated on each restart.Force the mock adapter
For demos or offline development:
LOCAL_AGENT_ADAPTER=mock npm run devThe mock streams a canned response with a synthetic tool call and a
synthetic diff that creates MOCK_NOTES.md — safe to "Apply" against
any real repo.
Verify the SDK dependency
A non-network sanity check that the OpenCode SDK installed correctly:
npm run verify-sdkDoes not spawn opencode and does not call any network APIs.
HTTP surface
All endpoints except GET /health require the
x-local-agent-token header.
| Method | Path | Purpose |
|--------|-------------------------------------|--------------------------------------------------------------------------------------|
| GET | /health | Liveness + version. Unauthenticated. |
| POST | /repo/select | Validate + bind a repo path. Body: { path }. |
| GET | /repo/status | Current selected repo (or { repo: null }). |
| GET | /repo/diff | Unified git diff HEAD + sizeBytes. |
| GET | /repo/changed-files | Parsed git status --short -uall. |
| POST | /repo/apply | Apply a unified diff. Two-phase: git apply --check then git apply. Body: { unified, baselineHash? }. |
| POST | /agent/message | SSE: stream AgentStreamEvent envelopes for one turn. Body: { sessionId, prompt, history? }. |
| POST | /agent/llm-response/:requestId | Browser fulfils a brokered LLM call. Body: { sessionId, text, provider, model, tokenUsage?, error? }. |
Brokered LLM calls
When an adapter calls request.broker(payload), the helper:
- Generates a
requestIdand stores a pending promise. - Emits an
llm_requestenvelope on the open/agent/messageSSE stream. - The browser hits
POST /api/coding/sessions/:id/llm-proxyon the Diologue cloud, which calls the user's configured LLM via the existing provider stack and writes ausage_ledgerrow. - The browser POSTs the response to
/agent/llm-response/:requestId, which fulfils the broker's promise. The adapter receives the text and continues iterating.
The broker registry is indexed by sessionId. A reconnecting stream
supersedes the prior broker (its pending requests reject with
superseded_by_new_stream).
The real
OpenCodeProcessAdapterdoes not yet route through the broker — opencode uses its own provider config. See "Roadmap".
Security posture
- Bound to
127.0.0.1only — never reachable from another host. - CORS allowlist is your web app origin (default
http://localhost:5000, override via env). Anything else gets no CORS headers; the browser drops the response. - Every endpoint except
GET /healthrequires thex-local-agent-tokenheader. Comparison is constant-time. - No shell-passthrough endpoints. Git operations use
execFile('git', [args], { cwd: validatedRepoPath }). - Repo paths are absolute, must exist, must contain
.git, and arerealpath-resolved to defeat symlink confusion. Stored verbatim is never trusted on subsequent calls. POST /repo/applyrunsgit apply --checkbefore writing. A failing check returns 409 with stderr; no filesystem changes.- The helper holds no cloud credentials. LLM calls are brokered back through the authenticated browser session.
TODO(tunnel-cloud-auth): the static pairing token is an MVP shortcut. When the cloud tunnel ships, this is replaced by a cloud-issued, rotated device credential.
Environment variables
| Var | Default | Purpose |
|----------------------------------|--------------------------|----------------------------------------------------------------------|
| LOCAL_AGENT_PORT | 4099 | TCP port to bind on 127.0.0.1. |
| LOCAL_AGENT_ALLOWED_ORIGIN | http://localhost:5000 | Single allowed CORS origin (the web app URL). |
| LOCAL_AGENT_TOKEN | (generated) | Override the auto-generated pairing token. For tests only. |
| LOCAL_AGENT_ADAPTER | (opencode) | Set to mock to force the mock adapter. Default uses opencode. |
| LOCAL_AGENT_ENGINE | local | Engine source. local (default, V5+) = bundled diologue-engine that postinstall fetches from GitHub Releases. npm = legacy opt-out (@opencode-ai/sdk + system opencode binary). |
| LOCAL_AGENT_ENGINE_BUNDLE | (auto-discovered) | Absolute path to a diologue-engine binary, overriding the postinstall-fetched one. For dev / CI. |
| SKIP_DIOLOGUE_POSTINSTALL | (unset) | Set to 1 to skip the postinstall bundle download (offline installs, CI). Helper still works via LOCAL_AGENT_ENGINE=npm. |
Testing
npm testUses Node's built-in test runner via tsx. Coverage:
lib/git.test.ts— parsesgit status --shortoutput (pure function, no shell).lib/paths.test.ts— repo-path validation against an on-disk scratch dir.routes/repo.test.ts— end-to-end/repo/*against a real git repository created inmkdtemp. Covers/repo/applyhappy path, rejection on a diff that doesn't apply, and 400 on invalid body.routes/agent.test.ts— SSE happy path with the mock adapter.routes/agent-broker.test.ts— end-to-end brokered LLM round-trip (the test code plays the role of the browser).broker.test.ts— unit tests forLlmBroker+BrokerRegistry.adapters/mock-adapter.test.ts— 10 conformance tests for theOpenCodeAdaptercontract. Real adapters MUST pass these.adapters/opencode-event-mapper.test.ts— pure-function event translator tests.adapters/opencode-adapter.test.ts— adapter shape + binary-check error path (skipped when opencode IS installed).
Today: 71/71 passing.
Roadmap
- [ ] Plumb opencode's outbound LLM calls through
request.brokerso coding sessions hit the cloud usage ledger. - [ ] Persistent opencode session ↔ Diologue session mapping across helper restarts.
- [ ] Synthesise full-file additions for untracked files into the
/repo/diffoutput. - [ ] Outbound tunnel to cloud relay for remote-machine support.
- [ ] Replace static pairing token with cloud-issued device credential.
- [ ] Deeper opencode rebrand — only if/when we vendor source rather than depend on the SDK.
Troubleshooting
"Helper isn't responding" in the web UI → the helper isn't running, the URL is wrong, or the port is in use. Start the helper and verify with:
curl http://localhost:4099/health"opencode binary not found on PATH" in chat error envelopes →
install via curl -fsSL https://opencode.ai/install | bash and confirm
opencode --version works in the same shell you're starting the helper
from. Or run with LOCAL_AGENT_ADAPTER=mock to bypass.
"Pairing token is missing or doesn't match" → the helper regenerates the token on every restart. Copy the new one from the console after each restart.
Diff "doesn't apply" → the working tree drifted between when the diff was proposed and when you clicked Apply. Refresh the diff (right-hand panel button) and re-prompt the agent.
