@luckydraw/cumulus 0.31.65 → 0.31.66
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/CHANGELOG.md +4 -0
- package/README.md +138 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Originally a CLI wrapper for Claude, Cumulus has grown into a full gateway platf
|
|
|
9
9
|
- **Gateway daemon** (`cumulus-gateway`) — HTTP + WebSocket server with per-thread conversations, streaming responses, and an admin API.
|
|
10
10
|
- **Web chat widget** — embeddable `/chat` interface with voice mode, push notifications, file uploads with progress, and rich [blex block](https://www.npmjs.com/package/@luckydraw/blex) rendering (tables, forms, charts, kanban, diagrams).
|
|
11
11
|
- **Channel adapters** — Slack and Discord bots, inbound email webhooks (Resend), and generic HTTP webhooks — all injecting into the same thread model.
|
|
12
|
+
- **Persistent web-app agents** — embed an agent into any web app that can _drive its UI_, answer questions about its data, and remember every conversation per visitor. See [Persistent web-app agents](#persistent-web-app-agents).
|
|
12
13
|
- **Inter-agent messaging** — threads can talk to each other via `send_to_agent`, with support for CC/BCC visibility.
|
|
13
14
|
- **Per-thread model selection** — Claude (via CLI) or any HuggingFace model (GLM-5, Kimi-K2.5, Qwen3, etc.) with tool calling.
|
|
14
15
|
- **Scheduled triggers, email, push, media serving** — built-in MCP tools so agents can send emails, schedule themselves, notify you, and upload files.
|
|
@@ -207,6 +208,139 @@ cumulus-gateway rollback # restores the previous version
|
|
|
207
208
|
|
|
208
209
|
The widget's top bar also shows an "Update available" indicator (with a manual ↻ check button) when a new version lands on npm.
|
|
209
210
|
|
|
211
|
+
## Persistent web-app agents
|
|
212
|
+
|
|
213
|
+
Cumulus can embed a persistent agent into any web app — one that **drives the app's UI**, **answers questions about the app and its data**, and **remembers every conversation per visitor**. No fork of the gateway, no per-app backend beyond a static file server.
|
|
214
|
+
|
|
215
|
+
Requires `>= 0.31.41`.
|
|
216
|
+
|
|
217
|
+
### How it works
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
Browser tab (your app) cumulus gateway
|
|
221
|
+
┌───────────────────────────────┐ ┌──────────────────────────────────┐
|
|
222
|
+
│ your app UI │ │ one thread per visitor: │
|
|
223
|
+
│ ├─ command registry │ wss │ myapp-<deviceId> │
|
|
224
|
+
│ │ (window.MyAppAgent) │ /bridge │ ├─ full history + RAG │
|
|
225
|
+
│ ├─ BridgeClient ────────────┼────────────▶│ ├─ per-thread config │
|
|
226
|
+
│ ├─ agent panel (chat UI) │ https │ │ (inherited from │
|
|
227
|
+
│ │ POST /api/thread/… ─────┼────────────▶│ │ myapp.config.json) │
|
|
228
|
+
│ └─ selection / right-click │ SSE │ └─ model turn per message │
|
|
229
|
+
│ feedback capture │ │ └─ MCP shim ──────────────┼──┐
|
|
230
|
+
└───────────────────────────────┘ └──────────────────────────────────┘ │
|
|
231
|
+
▲ │
|
|
232
|
+
POST /bridge/call ◀───────────────────┘
|
|
233
|
+
(agent tool call → executes in the tab)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Three moving parts:
|
|
237
|
+
|
|
238
|
+
1. **The gateway** — owns history, RAG retrieval, and prompt assembly, and runs a model turn per message. One config block per app; no code changes.
|
|
239
|
+
2. **Your front end** — registers a typed **command registry** (what the agent can see and do in the UI), mounts the **bridge client** (a WebSocket back to the gateway), and renders the agent panel.
|
|
240
|
+
3. **An MCP shim** — a small stdio script the gateway spawns per turn. It fetches the app's command manifest and exposes each command as a model-callable tool; calls are forwarded to `POST /bridge/call`, which dispatches them into the live browser tab.
|
|
241
|
+
|
|
242
|
+
The loop that makes the agent "drive the app": the model calls a tool → shim → `POST /bridge/call` → gateway pushes `call` over the tab's WebSocket → the tab executes it through the app's own actions (so guards, routing, and notifications all still fire) → the result flows back to the model.
|
|
243
|
+
|
|
244
|
+
Because the manifest is re-fetched every turn, **shipping a new UI command makes it agent-callable with zero gateway or backend changes.**
|
|
245
|
+
|
|
246
|
+
### Key concepts
|
|
247
|
+
|
|
248
|
+
| Concept | What it is |
|
|
249
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
250
|
+
| **Namespace** | A config block grouping an app's threads (`myapp-*`), scoping its API key, and carrying its per-app settings (proxy, MCP servers). |
|
|
251
|
+
| **Base thread** (`myapp`) | Your own management thread for the app, visible only to your admin key. Never used by visitors — but it _is_ the config template they inherit. |
|
|
252
|
+
| **Visitor threads** (`myapp-<id>`) | One per browser/device, minted client-side. Full persistent history + RAG each. Hidden from the default thread list. |
|
|
253
|
+
| **Bridge** | The gateway↔tab WebSocket: the tab registers its command manifest; the gateway dispatches agent tool calls into the tab. |
|
|
254
|
+
| **Command registry** | The app-side catalog of typed commands (`{ name, description, params, risk, execute }`) — one capability surface shared by the UI and the agent. |
|
|
255
|
+
| **Capability-by-name** | The security model: a scoped key can only touch threads in its namespace, can enumerate nothing, and the random thread name is the per-visitor secret. |
|
|
256
|
+
|
|
257
|
+
### Gateway setup
|
|
258
|
+
|
|
259
|
+
One edit to `~/.cumulus/gateway.config.json` (annotated below; the real file is strict JSON):
|
|
260
|
+
|
|
261
|
+
```jsonc
|
|
262
|
+
{
|
|
263
|
+
"bridge": { "enabled": true }, // global, default off — every bridge surface is inert until enabled
|
|
264
|
+
|
|
265
|
+
"namespaces": [
|
|
266
|
+
{
|
|
267
|
+
"name": "myapp", // covers threads matching myapp-*
|
|
268
|
+
"label": "My App",
|
|
269
|
+
"apiKeys": ["sk-myapp-<random>"], // the app's OWN key — mint a fresh one
|
|
270
|
+
|
|
271
|
+
// OPTIONAL: reverse-proxy selected paths to the app's backend through the
|
|
272
|
+
// gateway origin, so the front end needs only one origin.
|
|
273
|
+
"executorProxy": {
|
|
274
|
+
"origin": "http://127.0.0.1:8097",
|
|
275
|
+
"pathPrefixes": ["/state", "/journal"],
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// OPTIONAL, but required for "drive the app": the MCP shim that turns the
|
|
279
|
+
// app's command manifest into model-callable tools. Spawned per turn, only
|
|
280
|
+
// for threads in this namespace. {thread} is substituted with the real
|
|
281
|
+
// thread name (myapp-<deviceId>) in both args and env.
|
|
282
|
+
"extraMcpServers": {
|
|
283
|
+
"myapp-tools": {
|
|
284
|
+
"command": "node",
|
|
285
|
+
"args": ["/path/to/myapp/mcp-shim.js"],
|
|
286
|
+
"env": {
|
|
287
|
+
"GATEWAY_URL": "http://127.0.0.1:8090",
|
|
288
|
+
"GATEWAY_API_KEY": "sk-myapp-<same-scoped-key>",
|
|
289
|
+
"BRIDGE_THREAD": "{thread}",
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Then give visitor threads their persona and working directory via the base thread config, `~/.cumulus/threads/myapp.config.json`:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"projectDir": "/home/you/projects/myapp",
|
|
303
|
+
"model": "claude",
|
|
304
|
+
"effort": "high",
|
|
305
|
+
"alwaysInclude": ["docs/myapp-system-prompt.md"]
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
A turn on `myapp-a3f8c2d1` with no config of its own inherits this by prefix-fallback. Writes stay exact, so a visitor session can never mutate the base. `alwaysInclude` is where the app's product knowledge and persona live.
|
|
310
|
+
|
|
311
|
+
### Front-end integration
|
|
312
|
+
|
|
313
|
+
Your app ships four small pieces (all vanilla-JS-able, no framework required):
|
|
314
|
+
|
|
315
|
+
| Piece | Job |
|
|
316
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
317
|
+
| **Device thread id** | Mint `myapp-<random hex>` once, persist in `localStorage`. This _is_ the visitor's identity — use 16+ hex chars of entropy. |
|
|
318
|
+
| **Command registry** | Expose `window.MyAppAgent` with typed commands. Include a `describeView`-style command so the agent can read the current screen. |
|
|
319
|
+
| **Bridge mount** | Open `wss://<gateway>/bridge`, register the manifest, execute inbound `call` frames against the registry. |
|
|
320
|
+
| **Chat client** | `POST /api/thread/<thread>/message` and render the SSE stream. Mark risky commands so they route through a confirm step. |
|
|
321
|
+
|
|
322
|
+
Serve the gateway URL and scoped key to the browser at request time (e.g. `window.__APP_AGENT_CONFIG__` injected by your serving layer from an env var) — never commit the key into the repo.
|
|
323
|
+
|
|
324
|
+
### Security model
|
|
325
|
+
|
|
326
|
+
The browser must hold a credential — the bridge sends its key inside a WebSocket frame, which no reverse proxy can inject. So the model is _confinement_, not concealment:
|
|
327
|
+
|
|
328
|
+
- A **scoped key** can read/write only `myapp-*` threads; everything else is `403`.
|
|
329
|
+
- A scoped key **enumerates nothing** — `/api/threads`, `/api/agents`, and the dashboard all return empty. Reaching another visitor's thread means guessing its random name.
|
|
330
|
+
- The **thread name is the per-visitor secret**, the same capability-URL pattern as content-hashed `/media/*` filenames, one notch stricter (unguessable _and_ key-gated).
|
|
331
|
+
|
|
332
|
+
Mint fresh device ids with at least 16 hex characters; 8 is too thin against a determined brute-forcer.
|
|
333
|
+
|
|
334
|
+
### Checklist
|
|
335
|
+
|
|
336
|
+
1. Enable `bridge` and add the namespace + scoped key to the gateway config; reload.
|
|
337
|
+
2. Create the base thread config with `projectDir`, `model`, and the `alwaysInclude` system-prompt document.
|
|
338
|
+
3. Verify the scoped key: in-namespace `200`, out-of-namespace `403`, `/api/threads` empty.
|
|
339
|
+
4. Inject the gateway URL + scoped key into the page at serve time.
|
|
340
|
+
5. Ship the device thread id, command registry, bridge mount, and chat client.
|
|
341
|
+
6. Point the namespace's `extraMcpServers` shim at your manifest endpoint.
|
|
342
|
+
7. End-to-end check: ask the agent a question about the current screen, then ask it to navigate.
|
|
343
|
+
|
|
210
344
|
## Classic CLI mode
|
|
211
345
|
|
|
212
346
|
The original RLM chat loop still works. Great for quick terminal work without running the gateway.
|
|
@@ -291,28 +425,19 @@ All `/api/*` routes require `X-API-Key: <key>` (from `apiKeys[]`).
|
|
|
291
425
|
|
|
292
426
|
WebSocket (`/chat/ws`) carries the same semantics with streaming, interjection, `inject`, and voice-mode audio frames.
|
|
293
427
|
|
|
294
|
-
##
|
|
428
|
+
## Upgrading
|
|
295
429
|
|
|
296
430
|
```bash
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
npm install
|
|
300
|
-
npm run build
|
|
301
|
-
npm test # vitest
|
|
302
|
-
npm run lint
|
|
303
|
-
npm run type-check
|
|
431
|
+
npm install -g @luckydraw/cumulus@latest
|
|
432
|
+
cumulus-gateway reload # SIGHUP — drains active streams before restarting
|
|
304
433
|
```
|
|
305
434
|
|
|
306
|
-
-
|
|
307
|
-
- **Tests:** vitest, 700+ tests covering agentic loop, retriever, adapters, scheduler, push.
|
|
308
|
-
- **Deploy workflow:** bump version, `npm publish`, then `cumulus-gateway reload` on the host (SIGHUP drains active streams — see `docs/tasks/050-graceful-restart.md`).
|
|
435
|
+
Or use the built-in updater (`cumulus-gateway update` / `rollback`), which does the same thing and keeps the previous version for a one-command rollback. See [CHANGELOG.md](./CHANGELOG.md) for release notes.
|
|
309
436
|
|
|
310
437
|
## Background
|
|
311
438
|
|
|
312
439
|
Cumulus implements the **Recursive Language Model** pattern: treat conversation history as an external environment the model queries programmatically, rather than stuffing everything into context. This enables reasoning over contexts 2+ orders of magnitude beyond the model's window, with graceful cost scaling.
|
|
313
440
|
|
|
314
|
-
See `docs/` for task documents, ADRs, and implementation notes.
|
|
315
|
-
|
|
316
441
|
## License
|
|
317
442
|
|
|
318
443
|
MIT
|