@luckydraw/cumulus 0.31.65 → 1.0.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/CHANGELOG.md +6 -555
- package/LICENSE +150 -0
- package/README.md +161 -17
- package/dist/gateway/adapters/webchat.d.ts +2 -0
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +22 -2
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +17 -2
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +10 -3
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.d.ts +3 -1
- package/dist/gateway/daemon.d.ts.map +1 -1
- package/dist/gateway/daemon.js +128 -39
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/namespaces.d.ts +34 -0
- package/dist/gateway/namespaces.d.ts.map +1 -1
- package/dist/gateway/namespaces.js +58 -0
- package/dist/gateway/namespaces.js.map +1 -1
- package/dist/gateway/server.d.ts +8 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +150 -41
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/setup.d.ts +32 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +23 -3
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/static/widget.js +897 -611
- package/dist/lib/gateway.d.ts +30 -8
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +36 -11
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/history.d.ts +22 -0
- package/dist/lib/history.d.ts.map +1 -1
- package/dist/lib/history.js +59 -21
- package/dist/lib/history.js.map +1 -1
- package/dist/lib/huggingface-provider.d.ts.map +1 -1
- package/dist/lib/huggingface-provider.js +11 -3
- package/dist/lib/huggingface-provider.js.map +1 -1
- package/dist/lib/license.d.ts +76 -0
- package/dist/lib/license.d.ts.map +1 -0
- package/dist/lib/license.js +141 -0
- package/dist/lib/license.js.map +1 -0
- package/docs/agentic-harness-primer.md +283 -0
- package/docs/conditional-continuation.md +167 -0
- package/docs/web-app-agent-guide.md +520 -0
- package/examples/web-app-agent/README.md +187 -0
- package/examples/web-app-agent/agent/mcp-shim.js +105 -0
- package/examples/web-app-agent/gateway.config.example.json +52 -0
- package/examples/web-app-agent/package.json +13 -0
- package/examples/web-app-agent/public/agent/bridge-mount.js +75 -0
- package/examples/web-app-agent/public/agent/chat-client.js +104 -0
- package/examples/web-app-agent/public/agent/commands.js +250 -0
- package/examples/web-app-agent/public/agent/device-thread.js +48 -0
- package/examples/web-app-agent/public/agent/panel.css +107 -0
- package/examples/web-app-agent/public/agent/panel.js +369 -0
- package/examples/web-app-agent/public/app.js +250 -0
- package/examples/web-app-agent/public/index.html +111 -0
- package/examples/web-app-agent/server.js +242 -0
- package/package.json +7 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Cumulus Gateway Software License Agreement
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucky Draw LLC. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and its accompanying documentation ("the Software") are the
|
|
6
|
+
proprietary property of Lucky Draw LLC ("Licensor"). The Software is licensed,
|
|
7
|
+
not sold. Except for the limited rights expressly granted below, Licensor
|
|
8
|
+
reserves all right, title, and interest in and to the Software, including all
|
|
9
|
+
intellectual property rights.
|
|
10
|
+
|
|
11
|
+
Versions of this software published prior to 1.0.0 were released under the MIT
|
|
12
|
+
License. This Agreement governs version 1.0.0 and all later versions only; it
|
|
13
|
+
does not modify or revoke the terms under which any earlier version was
|
|
14
|
+
obtained.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
1. LIMITED FREE GRANT
|
|
19
|
+
|
|
20
|
+
Subject to your compliance with this Agreement, Licensor grants you a
|
|
21
|
+
non-exclusive, non-transferable, non-sublicensable, royalty-free, revocable
|
|
22
|
+
license to download, install, execute, and internally evaluate the Software
|
|
23
|
+
solely for:
|
|
24
|
+
|
|
25
|
+
(a) evaluation and assessment of the Software's suitability;
|
|
26
|
+
(b) development, prototyping, and testing;
|
|
27
|
+
(c) personal use; and
|
|
28
|
+
(d) other non-commercial use.
|
|
29
|
+
|
|
30
|
+
This grant includes the right to run the Software on your own machines and to
|
|
31
|
+
configure and integrate it for the purposes above.
|
|
32
|
+
|
|
33
|
+
2. USES REQUIRING A PAID LICENSE
|
|
34
|
+
|
|
35
|
+
You may NOT do any of the following without a separate written commercial
|
|
36
|
+
license agreement executed with Licensor:
|
|
37
|
+
|
|
38
|
+
(a) use the Software in production, or in support of any commercial,
|
|
39
|
+
business, or revenue-generating activity;
|
|
40
|
+
(b) use the Software to provide any product or service to a third party,
|
|
41
|
+
whether or not for a fee;
|
|
42
|
+
(c) host, operate, or otherwise make the Software available to third parties
|
|
43
|
+
as a hosted, managed, or software-as-a-service offering;
|
|
44
|
+
(d) redistribute, resell, lease, lend, or otherwise transfer the Software, in
|
|
45
|
+
whole or in part, alone or bundled with other software; or
|
|
46
|
+
(e) sublicense the Software to any third party.
|
|
47
|
+
|
|
48
|
+
To obtain a commercial license, contact ops@luckydrawdesign.com.
|
|
49
|
+
|
|
50
|
+
3. RESTRICTIONS
|
|
51
|
+
|
|
52
|
+
Under the free grant in Section 1, you may not:
|
|
53
|
+
|
|
54
|
+
(a) modify, adapt, translate, or create derivative works of the Software,
|
|
55
|
+
except as permitted by Section 3.1 (Example Code and Integration Code);
|
|
56
|
+
(b) reverse engineer, decompile, or disassemble the Software, or otherwise
|
|
57
|
+
attempt to derive its source code, except to the extent that applicable
|
|
58
|
+
law expressly permits this notwithstanding this limitation;
|
|
59
|
+
(c) remove, obscure, or alter any copyright, trademark, or other proprietary
|
|
60
|
+
notice contained in the Software;
|
|
61
|
+
(d) use Licensor's names, logos, or trademarks without prior written
|
|
62
|
+
permission; or
|
|
63
|
+
(e) disable, circumvent, or modify the license verification and demonstration
|
|
64
|
+
limits described in Section 4.
|
|
65
|
+
|
|
66
|
+
3.1 EXAMPLE CODE AND INTEGRATION CODE
|
|
67
|
+
|
|
68
|
+
The Software includes example, starter-kit, and template files (including,
|
|
69
|
+
without limitation, everything under the "examples/" directory) that the
|
|
70
|
+
Software's documentation directs you to copy into your own applications and
|
|
71
|
+
edit. Notwithstanding Section 3(a), you may copy, modify, and create derivative
|
|
72
|
+
works of those files, and you may distribute them as part of your own
|
|
73
|
+
application, without restriction under this Agreement.
|
|
74
|
+
|
|
75
|
+
You may also write, modify, and distribute your own code that configures,
|
|
76
|
+
calls, or otherwise interoperates with the Software, including code that runs
|
|
77
|
+
against its documented interfaces. Such code is yours.
|
|
78
|
+
|
|
79
|
+
This Section grants no rights in the rest of the Software. Copying or modifying
|
|
80
|
+
example code does not permit production or commercial use of the Software
|
|
81
|
+
itself, which continues to require a paid agreement under Section 2, and does
|
|
82
|
+
not permit anything restricted by Sections 3(b) through 3(e).
|
|
83
|
+
|
|
84
|
+
4. LICENSE KEYS AND DEMONSTRATION LIMITS
|
|
85
|
+
|
|
86
|
+
Without a valid license key, the Software operates in DEMONSTRATION MODE. In
|
|
87
|
+
demonstration mode:
|
|
88
|
+
|
|
89
|
+
(a) each configured thread namespace may create at most five (5) distinct
|
|
90
|
+
namespaced threads; requests that would create additional namespaced
|
|
91
|
+
threads are refused. Threads that already exist continue to function, and
|
|
92
|
+
no data is deleted or withheld;
|
|
93
|
+
(b) threads outside any configured namespace are not limited; and
|
|
94
|
+
(c) the Software records its unlicensed state in its logs.
|
|
95
|
+
|
|
96
|
+
A license key issued by Licensor removes these limits for the namespaces and the
|
|
97
|
+
major version it names. A key is valid only for the whole-number (major) release
|
|
98
|
+
for which it was issued; use with a different major release requires a new key.
|
|
99
|
+
|
|
100
|
+
License verification is performed locally. The Software does not transmit usage
|
|
101
|
+
information to Licensor.
|
|
102
|
+
|
|
103
|
+
Nothing in this Section grants any right to use the Software beyond the free
|
|
104
|
+
grant in Section 1. Demonstration mode is a convenience, not a license: use for
|
|
105
|
+
any purpose listed in Section 2 requires a paid license whether or not the
|
|
106
|
+
demonstration limits are reached.
|
|
107
|
+
|
|
108
|
+
5. THIRD-PARTY COMPONENTS
|
|
109
|
+
|
|
110
|
+
The Software depends on third-party open-source components, each governed by
|
|
111
|
+
its own license. Those licenses apply to those components, not to the Software,
|
|
112
|
+
and nothing in this Agreement limits your rights under them.
|
|
113
|
+
|
|
114
|
+
6. TERM AND TERMINATION
|
|
115
|
+
|
|
116
|
+
This Agreement takes effect when you first download, install, or use the
|
|
117
|
+
Software and continues until terminated. It terminates automatically and
|
|
118
|
+
immediately if you breach any term. Licensor may also terminate the free grant
|
|
119
|
+
in Section 1 at any time on written notice. On termination you must cease all
|
|
120
|
+
use of the Software and destroy all copies in your possession. Sections 3, 7,
|
|
121
|
+
8, and 9 survive termination.
|
|
122
|
+
|
|
123
|
+
7. NO WARRANTY
|
|
124
|
+
|
|
125
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
126
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
127
|
+
FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NONINFRINGEMENT. LICENSOR DOES NOT
|
|
128
|
+
WARRANT THAT THE SOFTWARE WILL BE UNINTERRUPTED, SECURE, OR ERROR-FREE.
|
|
129
|
+
|
|
130
|
+
8. LIMITATION OF LIABILITY
|
|
131
|
+
|
|
132
|
+
IN NO EVENT SHALL LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
133
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
|
|
134
|
+
OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
135
|
+
SOFTWARE. WITHOUT LIMITING THE FOREGOING, LICENSOR SHALL NOT BE LIABLE FOR ANY
|
|
136
|
+
INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES, OR FOR ANY
|
|
137
|
+
LOSS OF PROFITS, REVENUE, DATA, OR GOODWILL.
|
|
138
|
+
|
|
139
|
+
9. GENERAL
|
|
140
|
+
|
|
141
|
+
This Agreement is the entire agreement between you and Licensor regarding the
|
|
142
|
+
Software and supersedes all prior understandings on its subject matter. If any
|
|
143
|
+
provision is held unenforceable, it shall be modified to the minimum extent
|
|
144
|
+
necessary to make it enforceable and the remaining provisions shall remain in
|
|
145
|
+
full force. Licensor's failure to enforce any provision is not a waiver of it.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
Lucky Draw LLC
|
|
150
|
+
Licensing inquiries: ops@luckydrawdesign.com
|
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.
|
|
@@ -77,9 +78,11 @@ cumulus-gateway setup --project-root ~/projects --port 8080
|
|
|
77
78
|
# Start / stop / reload (if you skip the service install):
|
|
78
79
|
cumulus-gateway start
|
|
79
80
|
cumulus-gateway stop
|
|
80
|
-
cumulus-gateway reload #
|
|
81
|
+
cumulus-gateway reload # graceful restart — see the note below
|
|
81
82
|
```
|
|
82
83
|
|
|
84
|
+
**About `reload`:** it sends SIGHUP, which drains active streams (up to 120s) and then **exits the process**. A supervisor is what brings it back. If you installed the service, prefer your service manager — `sudo systemctl reload cumulus-gateway` (Linux) or `launchctl kickstart -k gui/$UID/com.luckydraw.cumulus` (macOS). If you started the gateway by hand, it will stay down; run `cumulus-gateway start` again.
|
|
85
|
+
|
|
83
86
|
Setup writes `~/.cumulus/gateway.config.json`, generates VAPID keys for push, scaffolds a systemd (Linux) or LaunchAgent (macOS) unit, and prints the generated API key.
|
|
84
87
|
|
|
85
88
|
Open `http://localhost:8080/chat`, paste the API key, and start talking. Messages hit your thread; responses stream back token-by-token.
|
|
@@ -110,7 +113,7 @@ Open `http://localhost:8080/chat`, paste the API key, and start talking. Message
|
|
|
110
113
|
}
|
|
111
114
|
```
|
|
112
115
|
|
|
113
|
-
Reload the daemon (`cumulus-gateway reload`
|
|
116
|
+
Reload the daemon after editing (`sudo systemctl reload cumulus-gateway`, or `cumulus-gateway reload` — see the note in Quickstart). It waits for active streams to finish first, so in-flight responses aren't dropped.
|
|
114
117
|
|
|
115
118
|
## Gateway features
|
|
116
119
|
|
|
@@ -189,7 +192,7 @@ Rate-limited per thread (default 10/hour). All sends are logged to thread histor
|
|
|
189
192
|
|
|
190
193
|
### Reliability
|
|
191
194
|
|
|
192
|
-
- **Graceful restart** —
|
|
195
|
+
- **Graceful restart** — SIGHUP drains active Claude/HF streams up to 120s, then exits for the supervisor to restart; no truncated responses on deploy.
|
|
193
196
|
- **Auto-resume after restart** — interrupted threads get a resume nudge on startup so the agent picks back up with full RAG context.
|
|
194
197
|
- **Persistent streaming buffer** — partial responses are flushed to disk every 5s during streaming and recovered on restart.
|
|
195
198
|
- **Truncation continuation** — `finish_reason: "length"` triggers max-token escalation (8k → 16k → 32k) and seamless continuation stitching.
|
|
@@ -207,6 +210,139 @@ cumulus-gateway rollback # restores the previous version
|
|
|
207
210
|
|
|
208
211
|
The widget's top bar also shows an "Update available" indicator (with a manual ↻ check button) when a new version lands on npm.
|
|
209
212
|
|
|
213
|
+
## Persistent web-app agents
|
|
214
|
+
|
|
215
|
+
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.
|
|
216
|
+
|
|
217
|
+
A runnable starter kit lives in [`examples/web-app-agent`](./examples/web-app-agent) — copy it and replace the demo app with yours.
|
|
218
|
+
|
|
219
|
+
### How it works
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
Browser tab (your app) cumulus gateway
|
|
223
|
+
┌───────────────────────────────┐ ┌──────────────────────────────────┐
|
|
224
|
+
│ your app UI │ │ one thread per visitor: │
|
|
225
|
+
│ ├─ command registry │ wss │ myapp-<deviceId> │
|
|
226
|
+
│ │ (window.MyAppAgent) │ /bridge │ ├─ full history + RAG │
|
|
227
|
+
│ ├─ BridgeClient ────────────┼────────────▶│ ├─ per-thread config │
|
|
228
|
+
│ ├─ agent panel (chat UI) │ https │ │ (inherited from │
|
|
229
|
+
│ │ POST /api/thread/… ─────┼────────────▶│ │ myapp.config.json) │
|
|
230
|
+
│ └─ selection / right-click │ SSE │ └─ model turn per message │
|
|
231
|
+
│ feedback capture │ │ └─ MCP shim ──────────────┼──┐
|
|
232
|
+
└───────────────────────────────┘ └──────────────────────────────────┘ │
|
|
233
|
+
▲ │
|
|
234
|
+
POST /bridge/call ◀───────────────────┘
|
|
235
|
+
(agent tool call → executes in the tab)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Three moving parts:
|
|
239
|
+
|
|
240
|
+
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.
|
|
241
|
+
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.
|
|
242
|
+
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.
|
|
243
|
+
|
|
244
|
+
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.
|
|
245
|
+
|
|
246
|
+
Because the manifest is re-fetched every turn, **shipping a new UI command makes it agent-callable with zero gateway or backend changes.**
|
|
247
|
+
|
|
248
|
+
### Key concepts
|
|
249
|
+
|
|
250
|
+
| Concept | What it is |
|
|
251
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
252
|
+
| **Namespace** | A config block grouping an app's threads (`myapp-*`), scoping its API key, and carrying its per-app settings (proxy, MCP servers). |
|
|
253
|
+
| **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. |
|
|
254
|
+
| **Visitor threads** (`myapp-<id>`) | One per browser/device, minted client-side. Full persistent history + RAG each. Hidden from the default thread list. |
|
|
255
|
+
| **Bridge** | The gateway↔tab WebSocket: the tab registers its command manifest; the gateway dispatches agent tool calls into the tab. |
|
|
256
|
+
| **Command registry** | The app-side catalog of typed commands (`{ name, description, params, risk, execute }`) — one capability surface shared by the UI and the agent. |
|
|
257
|
+
| **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. |
|
|
258
|
+
|
|
259
|
+
### Gateway setup
|
|
260
|
+
|
|
261
|
+
One edit to `~/.cumulus/gateway.config.json` (annotated below; the real file is strict JSON):
|
|
262
|
+
|
|
263
|
+
```jsonc
|
|
264
|
+
{
|
|
265
|
+
"bridge": { "enabled": true }, // global, default off — every bridge surface is inert until enabled
|
|
266
|
+
|
|
267
|
+
"namespaces": [
|
|
268
|
+
{
|
|
269
|
+
"name": "myapp", // covers threads matching myapp-*
|
|
270
|
+
"label": "My App",
|
|
271
|
+
"apiKeys": ["sk-myapp-<random>"], // the app's OWN key — mint a fresh one
|
|
272
|
+
|
|
273
|
+
// OPTIONAL: reverse-proxy selected paths to the app's backend through the
|
|
274
|
+
// gateway origin, so the front end needs only one origin.
|
|
275
|
+
"executorProxy": {
|
|
276
|
+
"origin": "http://127.0.0.1:8097",
|
|
277
|
+
"pathPrefixes": ["/state", "/journal"],
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// OPTIONAL, but required for "drive the app": the MCP shim that turns the
|
|
281
|
+
// app's command manifest into model-callable tools. Spawned per turn, only
|
|
282
|
+
// for threads in this namespace. {thread} is substituted with the real
|
|
283
|
+
// thread name (myapp-<deviceId>) in both args and env.
|
|
284
|
+
"extraMcpServers": {
|
|
285
|
+
"myapp-tools": {
|
|
286
|
+
"command": "node",
|
|
287
|
+
"args": ["/path/to/myapp/mcp-shim.js"],
|
|
288
|
+
"env": {
|
|
289
|
+
"GATEWAY_ORIGIN": "http://127.0.0.1:8080",
|
|
290
|
+
"GATEWAY_API_KEY": "sk-myapp-<same-scoped-key>",
|
|
291
|
+
"BRIDGE_THREAD": "{thread}",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Then give visitor threads their persona and working directory via the base thread config, `~/.cumulus/threads/myapp.config.json`:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"projectDir": "/home/you/projects/myapp",
|
|
305
|
+
"model": "claude",
|
|
306
|
+
"effort": "high",
|
|
307
|
+
"alwaysInclude": ["docs/myapp-system-prompt.md"]
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
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.
|
|
312
|
+
|
|
313
|
+
### Front-end integration
|
|
314
|
+
|
|
315
|
+
Your app ships four small pieces (all vanilla-JS-able, no framework required):
|
|
316
|
+
|
|
317
|
+
| Piece | Job |
|
|
318
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
319
|
+
| **Device thread id** | Mint `myapp-<random hex>` once, persist in `localStorage`. This _is_ the visitor's identity — use 16+ hex chars of entropy. |
|
|
320
|
+
| **Command registry** | Expose `window.MyAppAgent` with typed commands. Include a `describeView`-style command so the agent can read the current screen. |
|
|
321
|
+
| **Bridge mount** | Open `wss://<gateway>/bridge`, register the manifest, execute inbound `call` frames against the registry. |
|
|
322
|
+
| **Chat client** | `POST /api/thread/<thread>/message` and render the SSE stream. Mark risky commands so they route through a confirm step. |
|
|
323
|
+
|
|
324
|
+
Serve the gateway URL and scoped key from a **session-gated endpoint** your app already protects (`GET /api/agent-config` → `401` until the visitor is logged in), and fetch it at runtime. Never commit the key into the repo, and prefer this over injecting it into the served HTML: injected config ships the key to every visitor of the page, including anonymous ones, and it lands in caches and view-source. The starter kit in [`examples/web-app-agent`](./examples/web-app-agent) does it the gated way.
|
|
325
|
+
|
|
326
|
+
### Security model
|
|
327
|
+
|
|
328
|
+
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:
|
|
329
|
+
|
|
330
|
+
- A **scoped key** can read/write only `myapp-*` threads; everything else is `403`.
|
|
331
|
+
- 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.
|
|
332
|
+
- 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).
|
|
333
|
+
|
|
334
|
+
Mint fresh device ids with at least 16 hex characters; 8 is too thin against a determined brute-forcer.
|
|
335
|
+
|
|
336
|
+
### Checklist
|
|
337
|
+
|
|
338
|
+
1. Enable `bridge` and add the namespace + scoped key to the gateway config; reload.
|
|
339
|
+
2. Create the base thread config with `projectDir`, `model`, and the `alwaysInclude` system-prompt document.
|
|
340
|
+
3. Verify the scoped key: in-namespace `200`, out-of-namespace `403`, `/api/threads` empty.
|
|
341
|
+
4. Inject the gateway URL + scoped key into the page at serve time.
|
|
342
|
+
5. Ship the device thread id, command registry, bridge mount, and chat client.
|
|
343
|
+
6. Point the namespace's `extraMcpServers` shim at your manifest endpoint.
|
|
344
|
+
7. End-to-end check: ask the agent a question about the current screen, then ask it to navigate.
|
|
345
|
+
|
|
210
346
|
## Classic CLI mode
|
|
211
347
|
|
|
212
348
|
The original RLM chat loop still works. Great for quick terminal work without running the gateway.
|
|
@@ -291,28 +427,36 @@ All `/api/*` routes require `X-API-Key: <key>` (from `apiKeys[]`).
|
|
|
291
427
|
|
|
292
428
|
WebSocket (`/chat/ws`) carries the same semantics with streaming, interjection, `inject`, and voice-mode audio frames.
|
|
293
429
|
|
|
294
|
-
##
|
|
430
|
+
## Upgrading
|
|
295
431
|
|
|
296
432
|
```bash
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
npm install
|
|
300
|
-
npm run build
|
|
301
|
-
npm test # vitest
|
|
302
|
-
npm run lint
|
|
303
|
-
npm run type-check
|
|
433
|
+
npm install -g @luckydraw/cumulus@latest
|
|
434
|
+
sudo systemctl reload cumulus-gateway # or `cumulus-gateway reload` if self-managed
|
|
304
435
|
```
|
|
305
436
|
|
|
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`).
|
|
437
|
+
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
438
|
|
|
310
439
|
## Background
|
|
311
440
|
|
|
312
441
|
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
442
|
|
|
314
|
-
See `docs/` for task documents, ADRs, and implementation notes.
|
|
315
|
-
|
|
316
443
|
## License
|
|
317
444
|
|
|
318
|
-
|
|
445
|
+
Cumulus is **proprietary software**, copyright © 2026 Lucky Draw LLC. It is licensed, not sold. See [LICENSE](./LICENSE) for the full terms — the summary:
|
|
446
|
+
|
|
447
|
+
| | |
|
|
448
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
449
|
+
| **Free, no agreement needed** | Evaluation, development, prototyping, testing, personal and other non-commercial use. Install it, run it, integrate it, try it against your own app. |
|
|
450
|
+
| **Requires a paid commercial license** | Production use · commercial or revenue-generating use · providing a product or service to third parties · hosting it as a managed/SaaS offering · redistribution or resale · sublicensing. |
|
|
451
|
+
|
|
452
|
+
**Demonstration mode.** Without a license key, cumulus runs fully but each configured thread namespace may create at most **5 namespaced threads** — enough to evaluate a web-app agent end to end, not enough to serve real visitors. Threads outside a namespace (your own threads, the CLI, the TUI) are never limited, and existing threads keep working when a namespace is at its limit. Add a key to `gateway.config.json` and reload:
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{ "licenseKey": "cumulus-lic-v1...." }
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Keys are verified offline — nothing is transmitted — and are issued per whole-number release: a `1.x` key covers every `1.x.y`.
|
|
459
|
+
|
|
460
|
+
Commercial licensing: **ops@luckydrawdesign.com**
|
|
461
|
+
|
|
462
|
+
Versions up to and including `0.31.66` were published under the MIT License and remain available under those terms. This license applies to `1.0.0` and later.
|
|
@@ -14,6 +14,8 @@ export interface WebChatAdapterConfig {
|
|
|
14
14
|
apiKeys: string[];
|
|
15
15
|
/** Thread namespaces — prefix-scoped list visibility for app/test threads (task 097 P2). */
|
|
16
16
|
namespaces?: NamespaceConfig[];
|
|
17
|
+
/** Lucky Draw licence key (task 127) — absent = demo mode, namespaces capped. */
|
|
18
|
+
licenseKey?: string;
|
|
17
19
|
/** Gateway pipeline options (basePath, claudePath, sharedMcpPort) */
|
|
18
20
|
pipelineOpts?: Partial<Pick<MessagePipelineOptions, 'basePath' | 'claudePath' | 'sharedMcpPort' | 'projectRoot' | 'gatewayAgentsConfig' | 'hfApiKey' | 'openaiApiKey' | 'models' | 'claudeModels'>>;
|
|
19
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webchat.d.ts","sourceRoot":"","sources":["../../../src/gateway/adapters/webchat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAe7B,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"webchat.d.ts","sourceRoot":"","sources":["../../../src/gateway/adapters/webchat.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAe7B,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,sBAAsB,CAAC;AAS9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAqBpD,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IACpB,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CACpB,IAAI,CACF,sBAAsB,EACpB,UAAU,GACV,YAAY,GACZ,eAAe,GACf,aAAa,GACb,qBAAqB,GACrB,UAAU,GACV,cAAc,GACd,QAAQ,GACR,cAAc,CACjB,CACF,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,oFAAoF;IACpF,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAChF,6EAA6E;IAC7E,uBAAuB,EAAE,MAAM,MAAM,EAAE,CAAC;CACzC;AAoLD,mCAAmC;AACnC,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CA2ET;AA2DD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAoXvF"}
|
|
@@ -12,10 +12,12 @@ import { WebSocketServer } from 'ws';
|
|
|
12
12
|
import { listAlwaysIncludeFiles, addAlwaysIncludeFile, removeAlwaysIncludeFile, saveThreadConfig, loadThreadConfig, } from '../../lib/config.js';
|
|
13
13
|
import { loadEmbeddings, generateMissingEmbeddings } from '../../lib/embeddings.js';
|
|
14
14
|
import { sendMessage, getOrCreateThread, clearThreadCache, } from '../../lib/gateway.js';
|
|
15
|
+
import { verifyLicense, majorOf } from '../../lib/license.js';
|
|
15
16
|
import { executeRevert } from '../../lib/revert.js';
|
|
16
17
|
import { catchUpSegmentation, loadSegmentBoundaries, saveSegmentBoundaries, } from '../../lib/segments.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
18
|
+
import { getCurrentVersion } from '../../lib/version-check.js';
|
|
19
|
+
import { allNamespaceKeys, demoMintDenial, extraMcpServersForThread, namespaceForKey, threadAccessibleToScope, threadVisibleToScope, } from '../namespaces.js';
|
|
20
|
+
import { markThreadBusy, enqueueAgentMessage, formatAgentMessageForRecipient, isAgentSender, demoCapMessage, listExistingThreadNames, } from '../server.js';
|
|
19
21
|
import { VoiceTTSSession, isPiperAvailable, SAMPLE_RATE } from '../voice-tts.js';
|
|
20
22
|
// ─── Rate limiting ───────────────────────────────────────────────────────────
|
|
21
23
|
const RATE_LIMIT_WINDOW = 60_000; // 1 minute
|
|
@@ -198,6 +200,9 @@ export function createWebChatAdapter(config) {
|
|
|
198
200
|
const { server, pipelineOpts } = config;
|
|
199
201
|
const namespaces = config.namespaces ?? [];
|
|
200
202
|
resolvedNamespaces = namespaces;
|
|
203
|
+
// Task 127: same offline verification as the REST server, so both transports
|
|
204
|
+
// agree. No banner here — the server logs it once for the whole gateway.
|
|
205
|
+
const licenseStatus = verifyLicense(config.licenseKey, majorOf(getCurrentVersion()));
|
|
201
206
|
// Namespace-scoped keys (task 097 P2) authenticate like base keys.
|
|
202
207
|
const apiKeys = Array.from(new Set([...config.apiKeys, ...allNamespaceKeys(namespaces)]));
|
|
203
208
|
// Set module-level basePath for functions defined outside this closure
|
|
@@ -365,6 +370,21 @@ export function createWebChatAdapter(config) {
|
|
|
365
370
|
});
|
|
366
371
|
return;
|
|
367
372
|
}
|
|
373
|
+
// Demo capacity gate (task 127) — the WS mirror of the REST 402. Same
|
|
374
|
+
// ordering rationale: after the access check, so occupancy never leaks to
|
|
375
|
+
// an out-of-namespace caller.
|
|
376
|
+
if (typeof wsThread === 'string' && wsThread.length > 0) {
|
|
377
|
+
const capDenial = demoMintDenial(wsThread, namespaces, listExistingThreadNames(), licenseStatus);
|
|
378
|
+
if (capDenial) {
|
|
379
|
+
wsSend(ws, {
|
|
380
|
+
type: 'error',
|
|
381
|
+
error: demoCapMessage(capDenial),
|
|
382
|
+
demoMode: true,
|
|
383
|
+
threadName: wsThread,
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
368
388
|
// Handle history request
|
|
369
389
|
if (data.type === 'history') {
|
|
370
390
|
if (data.threadName)
|