@diologue/local-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -0
- package/dist/cli.mjs +2527 -0
- package/dist/cli.mjs.map +7 -0
- package/dist/engine-release.json +13 -0
- package/dist/postinstall.mjs +171 -0
- package/engine-release.json +13 -0
- package/package.json +52 -0
- package/scripts/postinstall.ts +191 -0
- package/src/lib/engine-bundle.ts +123 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# `@diologue/local-agent`
|
|
2
|
+
|
|
3
|
+
Local helper that bridges the Diologue **Coding Agent** web UI to an
|
|
4
|
+
[OpenCode](https://opencode.ai/) process running against a user-selected
|
|
5
|
+
git repo on the same machine.
|
|
6
|
+
|
|
7
|
+
## Quickstart (recommended)
|
|
8
|
+
|
|
9
|
+
Visit `/coding-agent/quickstart` on your Diologue deployment in a
|
|
10
|
+
browser. It will issue a one-time setup token and hand you a single
|
|
11
|
+
command to paste in a terminal:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @diologue/local-agent --cloud=<your-cloud> --token=<setup-token>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The helper starts, calls back to the cloud, the browser tab redirects
|
|
18
|
+
itself, and the coding agent is ready. No env vars, no copy-paste of
|
|
19
|
+
URLs or tokens into the UI.
|
|
20
|
+
|
|
21
|
+
## Standalone / power-user mode
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Run the helper directly, get a random pairing token to paste in
|
|
25
|
+
diologue-local-agent start --origin=https://your-app.com
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**This is v1 of a hybrid architecture.** It binds to `127.0.0.1` only and
|
|
29
|
+
is intended to be reached by the browser running on the same machine.
|
|
30
|
+
A future version will open an authenticated tunnel back to the cloud so
|
|
31
|
+
the same UI can drive a remote machine — the HTTP surface is designed to
|
|
32
|
+
be compatible with that evolution.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
┌──────────────────┐ HTTPS ┌──────────────────┐
|
|
40
|
+
│ Diologue web UI │ ──────────────▶ │ Diologue cloud │ Existing app:
|
|
41
|
+
│ (your browser) │ ◀────────────── │ (Express + LLMs) │ auth, billing,
|
|
42
|
+
└──────────────────┘ └──────────────────┘ session storage
|
|
43
|
+
│ ▲
|
|
44
|
+
│ http://127.0.0.1:4099 │ llm_request brokered
|
|
45
|
+
│ (this helper) │ back through the
|
|
46
|
+
▼ │ browser → cloud LLM
|
|
47
|
+
┌──────────────────┐ @opencode-ai/sdk │ proxy → usage_ledger
|
|
48
|
+
│ Local Agent │ ─────────────────────────┘
|
|
49
|
+
│ (this package) │
|
|
50
|
+
└──────────────────┘
|
|
51
|
+
│ spawns/talks to
|
|
52
|
+
▼
|
|
53
|
+
┌──────────────────┐
|
|
54
|
+
│ opencode binary │ Installed separately — see Prerequisites.
|
|
55
|
+
│ on your machine │
|
|
56
|
+
└──────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Prerequisites
|
|
62
|
+
|
|
63
|
+
1. **Node 20+** (matches the parent app).
|
|
64
|
+
2. **OpenCode binary** on `PATH`. The official installer:
|
|
65
|
+
```bash
|
|
66
|
+
curl -fsSL https://opencode.ai/install | bash
|
|
67
|
+
```
|
|
68
|
+
Verify: `opencode --version`.
|
|
69
|
+
|
|
70
|
+
You can run the helper without opencode by forcing the mock adapter
|
|
71
|
+
(see "Run" below) — useful for demos.
|
|
72
|
+
|
|
73
|
+
3. **A provider key for opencode** — opencode reads its own config (env
|
|
74
|
+
vars / `~/.config/opencode/...`). Today the helper uses opencode's
|
|
75
|
+
native provider config for the actual model calls; the cloud LLM
|
|
76
|
+
proxy + usage ledger are wired but not yet plumbed through opencode
|
|
77
|
+
itself (see "Roadmap").
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# For end users — no clone required
|
|
85
|
+
npx @diologue/local-agent --help
|
|
86
|
+
|
|
87
|
+
# For development on this repo
|
|
88
|
+
cd local-agent && npm install
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The published npm package is a single-file bundle produced by
|
|
92
|
+
`npm run build`. End users never need to clone the repo.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Run
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cd local-agent
|
|
100
|
+
npm run dev # tsx watch — auto-reloads on edits
|
|
101
|
+
# or
|
|
102
|
+
npm start
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The helper listens on `http://127.0.0.1:4099` and prints a one-time
|
|
106
|
+
pairing token to the console. Paste that token into the Coding Agent
|
|
107
|
+
page in the web UI to connect.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
[local-agent] Listening on http://127.0.0.1:4099
|
|
111
|
+
[local-agent] Adapter: opencode
|
|
112
|
+
[local-agent] Pairing token (paste into the web UI):
|
|
113
|
+
[local-agent] <random-token-here>
|
|
114
|
+
[local-agent] Allowed browser origin: http://localhost:5000
|
|
115
|
+
[local-agent] Press Ctrl+C to stop. The token is regenerated on each restart.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Force the mock adapter
|
|
119
|
+
|
|
120
|
+
For demos or offline development:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
LOCAL_AGENT_ADAPTER=mock npm run dev
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The mock streams a canned response with a synthetic tool call and a
|
|
127
|
+
synthetic diff that creates `MOCK_NOTES.md` — safe to "Apply" against
|
|
128
|
+
any real repo.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Verify the SDK dependency
|
|
133
|
+
|
|
134
|
+
A non-network sanity check that the OpenCode SDK installed correctly:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm run verify-sdk
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Does **not** spawn opencode and does **not** call any network APIs.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## HTTP surface
|
|
145
|
+
|
|
146
|
+
All endpoints except `GET /health` require the
|
|
147
|
+
`x-local-agent-token` header.
|
|
148
|
+
|
|
149
|
+
| Method | Path | Purpose |
|
|
150
|
+
|--------|-------------------------------------|--------------------------------------------------------------------------------------|
|
|
151
|
+
| GET | `/health` | Liveness + version. Unauthenticated. |
|
|
152
|
+
| POST | `/repo/select` | Validate + bind a repo path. Body: `{ path }`. |
|
|
153
|
+
| GET | `/repo/status` | Current selected repo (or `{ repo: null }`). |
|
|
154
|
+
| GET | `/repo/diff` | Unified `git diff HEAD` + `sizeBytes`. |
|
|
155
|
+
| GET | `/repo/changed-files` | Parsed `git status --short -uall`. |
|
|
156
|
+
| POST | `/repo/apply` | Apply a unified diff. Two-phase: `git apply --check` then `git apply`. Body: `{ unified, baselineHash? }`. |
|
|
157
|
+
| POST | `/agent/message` | SSE: stream `AgentStreamEvent` envelopes for one turn. Body: `{ sessionId, prompt, history? }`. |
|
|
158
|
+
| POST | `/agent/llm-response/:requestId` | Browser fulfils a brokered LLM call. Body: `{ sessionId, text, provider, model, tokenUsage?, error? }`. |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Brokered LLM calls
|
|
163
|
+
|
|
164
|
+
When an adapter calls `request.broker(payload)`, the helper:
|
|
165
|
+
|
|
166
|
+
1. Generates a `requestId` and stores a pending promise.
|
|
167
|
+
2. Emits an `llm_request` envelope on the open `/agent/message` SSE
|
|
168
|
+
stream.
|
|
169
|
+
3. The browser hits `POST /api/coding/sessions/:id/llm-proxy` on the
|
|
170
|
+
Diologue cloud, which calls the user's configured LLM via the
|
|
171
|
+
existing provider stack and writes a `usage_ledger` row.
|
|
172
|
+
4. The browser POSTs the response to
|
|
173
|
+
`/agent/llm-response/:requestId`, which fulfils the broker's
|
|
174
|
+
promise. The adapter receives the text and continues iterating.
|
|
175
|
+
|
|
176
|
+
The broker registry is indexed by `sessionId`. A reconnecting stream
|
|
177
|
+
supersedes the prior broker (its pending requests reject with
|
|
178
|
+
`superseded_by_new_stream`).
|
|
179
|
+
|
|
180
|
+
> The **real `OpenCodeProcessAdapter` does not yet route through the
|
|
181
|
+
> broker** — opencode uses its own provider config. See "Roadmap".
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Security posture
|
|
186
|
+
|
|
187
|
+
- Bound to `127.0.0.1` only — never reachable from another host.
|
|
188
|
+
- CORS allowlist is your web app origin (default
|
|
189
|
+
`http://localhost:5000`, override via env). Anything else gets no
|
|
190
|
+
CORS headers; the browser drops the response.
|
|
191
|
+
- Every endpoint except `GET /health` requires the
|
|
192
|
+
`x-local-agent-token` header. Comparison is constant-time.
|
|
193
|
+
- No shell-passthrough endpoints. Git operations use
|
|
194
|
+
`execFile('git', [args], { cwd: validatedRepoPath })`.
|
|
195
|
+
- Repo paths are absolute, must exist, must contain `.git`, and are
|
|
196
|
+
`realpath`-resolved to defeat symlink confusion. Stored verbatim is
|
|
197
|
+
never trusted on subsequent calls.
|
|
198
|
+
- `POST /repo/apply` runs `git apply --check` before writing. A failing
|
|
199
|
+
check returns 409 with stderr; no filesystem changes.
|
|
200
|
+
- The helper holds no cloud credentials. LLM calls are brokered back
|
|
201
|
+
through the authenticated browser session.
|
|
202
|
+
|
|
203
|
+
> **TODO(tunnel-cloud-auth):** the static pairing token is an MVP
|
|
204
|
+
> shortcut. When the cloud tunnel ships, this is replaced by a
|
|
205
|
+
> cloud-issued, rotated device credential.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Environment variables
|
|
210
|
+
|
|
211
|
+
| Var | Default | Purpose |
|
|
212
|
+
|----------------------------------|--------------------------|----------------------------------------------------------------------|
|
|
213
|
+
| `LOCAL_AGENT_PORT` | `4099` | TCP port to bind on `127.0.0.1`. |
|
|
214
|
+
| `LOCAL_AGENT_ALLOWED_ORIGIN` | `http://localhost:5000` | Single allowed CORS origin (the web app URL). |
|
|
215
|
+
| `LOCAL_AGENT_TOKEN` | _(generated)_ | Override the auto-generated pairing token. **For tests only.** |
|
|
216
|
+
| `LOCAL_AGENT_ADAPTER` | _(opencode)_ | Set to `mock` to force the mock adapter. Default uses opencode. |
|
|
217
|
+
| `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). |
|
|
218
|
+
| `LOCAL_AGENT_ENGINE_BUNDLE` | _(auto-discovered)_ | Absolute path to a diologue-engine binary, overriding the postinstall-fetched one. For dev / CI. |
|
|
219
|
+
| `SKIP_DIOLOGUE_POSTINSTALL` | _(unset)_ | Set to `1` to skip the postinstall bundle download (offline installs, CI). Helper still works via `LOCAL_AGENT_ENGINE=npm`. |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Testing
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npm test
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Uses Node's built-in test runner via `tsx`. Coverage:
|
|
230
|
+
|
|
231
|
+
- `lib/git.test.ts` — parses `git status --short` output (pure
|
|
232
|
+
function, no shell).
|
|
233
|
+
- `lib/paths.test.ts` — repo-path validation against an on-disk
|
|
234
|
+
scratch dir.
|
|
235
|
+
- `routes/repo.test.ts` — end-to-end `/repo/*` against a real git
|
|
236
|
+
repository created in `mkdtemp`. Covers `/repo/apply` happy path,
|
|
237
|
+
rejection on a diff that doesn't apply, and 400 on invalid body.
|
|
238
|
+
- `routes/agent.test.ts` — SSE happy path with the mock adapter.
|
|
239
|
+
- `routes/agent-broker.test.ts` — end-to-end brokered LLM round-trip
|
|
240
|
+
(the test code plays the role of the browser).
|
|
241
|
+
- `broker.test.ts` — unit tests for `LlmBroker` + `BrokerRegistry`.
|
|
242
|
+
- `adapters/mock-adapter.test.ts` — 10 conformance tests for the
|
|
243
|
+
`OpenCodeAdapter` contract. Real adapters MUST pass these.
|
|
244
|
+
- `adapters/opencode-event-mapper.test.ts` — pure-function event
|
|
245
|
+
translator tests.
|
|
246
|
+
- `adapters/opencode-adapter.test.ts` — adapter shape + binary-check
|
|
247
|
+
error path (skipped when opencode IS installed).
|
|
248
|
+
|
|
249
|
+
Today: **71/71 passing**.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Roadmap
|
|
254
|
+
|
|
255
|
+
- [ ] Plumb opencode's outbound LLM calls through `request.broker` so
|
|
256
|
+
coding sessions hit the cloud usage ledger.
|
|
257
|
+
- [ ] Persistent opencode session ↔ Diologue session mapping across
|
|
258
|
+
helper restarts.
|
|
259
|
+
- [ ] Synthesise full-file additions for untracked files into the
|
|
260
|
+
`/repo/diff` output.
|
|
261
|
+
- [ ] Outbound tunnel to cloud relay for remote-machine support.
|
|
262
|
+
- [ ] Replace static pairing token with cloud-issued device credential.
|
|
263
|
+
- [ ] Deeper opencode rebrand — only if/when we vendor source rather
|
|
264
|
+
than depend on the SDK.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Troubleshooting
|
|
269
|
+
|
|
270
|
+
**"Helper isn't responding"** in the web UI → the helper isn't
|
|
271
|
+
running, the URL is wrong, or the port is in use. Start the helper and
|
|
272
|
+
verify with:
|
|
273
|
+
```bash
|
|
274
|
+
curl http://localhost:4099/health
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**"opencode binary not found on PATH"** in chat error envelopes →
|
|
278
|
+
install via `curl -fsSL https://opencode.ai/install | bash` and confirm
|
|
279
|
+
`opencode --version` works in the same shell you're starting the helper
|
|
280
|
+
from. Or run with `LOCAL_AGENT_ADAPTER=mock` to bypass.
|
|
281
|
+
|
|
282
|
+
**"Pairing token is missing or doesn't match"** → the helper
|
|
283
|
+
regenerates the token on every restart. Copy the new one from the
|
|
284
|
+
console after each restart.
|
|
285
|
+
|
|
286
|
+
**Diff "doesn't apply"** → the working tree drifted between when the
|
|
287
|
+
diff was proposed and when you clicked Apply. Refresh the diff
|
|
288
|
+
(right-hand panel button) and re-prompt the agent.
|