@anusornneal/chat-relay 0.8.0 → 0.9.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 +339 -336
- package/agent/capability-scheduler.mjs +121 -121
- package/agent/connection-state.mjs +141 -126
- package/agent/desktop-manager.mjs +424 -440
- package/agent/file-manager.mjs +527 -527
- package/agent/lifecycle.mjs +101 -101
- package/agent/local-agent.mjs +494 -475
- package/agent/macos-desktop-helper.js +243 -0
- package/agent/macos-desktop-runner.mjs +292 -0
- package/agent/process-manager.mjs +110 -26
- package/agent/protocol.mjs +67 -67
- package/agent/terminal-manager.mjs +587 -550
- package/agent/windows-desktop-runner.mjs +109 -0
- package/agent/windows-desktop-worker.ps1 +582 -508
- package/cli/config.mjs +114 -114
- package/cli/device-login.mjs +170 -170
- package/cli/index.mjs +241 -241
- package/cli/remote.mjs +139 -139
- package/package.json +76 -75
- package/agent/windows-desktop.ps1 +0 -288
package/README.md
CHANGED
|
@@ -1,339 +1,342 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Cloudflare Worker + Durable Objects relay that exposes one or more local Windows agents to ChatGPT through remote MCP.
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
ChatGPT / MCP client
|
|
7
|
-
|
|
|
8
|
-
| Streamable HTTP MCP
|
|
9
|
-
v
|
|
10
|
-
Cloudflare Worker
|
|
11
|
-
|
|
|
12
|
-
+-- Registry Durable Object (users, agents, grants)
|
|
13
|
-
|
|
|
14
|
-
+-- Relay Durable Object per agent
|
|
15
|
-
|
|
|
16
|
-
| WebSocket
|
|
17
|
-
v
|
|
18
|
-
Local Agent
|
|
19
|
-
- terminal
|
|
20
|
-
- files
|
|
21
|
-
- processes
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The relay is generic and has no Devflow-specific logic.
|
|
25
|
-
|
|
26
|
-
## Quick start
|
|
27
|
-
|
|
28
|
-
The CLI is published on npm. You do **not** need to clone this repository or run `npm install`.
|
|
29
|
-
|
|
30
|
-
From any directory:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npx @anusornneal/chat-relay@latest remote
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
On the first run, Chat Relay opens a browser-based device login. Sign in, approve the computer, then return to the terminal. The CLI saves its credentials in your user profile and connects the local agent automatically.
|
|
37
|
-
|
|
38
|
-
Later, use the same command from anywhere:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx @anusornneal/chat-relay@latest remote
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
A successful connection looks like:
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
Chat Relay Remote
|
|
48
|
-
-----------------
|
|
49
|
-
Agent: Primary PC (default)
|
|
50
|
-
Terminal: enabled
|
|
51
|
-
Desktop: disabled
|
|
52
|
-
Agent connected
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Zero-checkout CLI
|
|
56
|
-
|
|
57
|
-
Other commands:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
npx @anusornneal/chat-relay@latest login
|
|
61
|
-
npx @anusornneal/chat-relay@latest status
|
|
62
|
-
npx @anusornneal/chat-relay@latest logout
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
On Windows the config defaults to `%LOCALAPPDATA%\chat-relay\config.json`. On macOS/Linux it uses `$XDG_CONFIG_HOME/chat-relay/config.json` or `~/.config/chat-relay/config.json`. Set `CHAT_RELAY_HOME` to override the location.
|
|
66
|
-
|
|
67
|
-
Useful options:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
chat-relay remote --root "C:\Users\you\Projects"
|
|
71
|
-
chat-relay login --name "Work PC"
|
|
72
|
-
chat-relay login --no-open
|
|
73
|
-
chat-relay remote --desktop
|
|
74
|
-
chat-relay remote --no-desktop
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The published package locates its bundled local agent relative to the package itself, so commands work from any working directory. Existing repository users can still run `npm start`; legacy `.dev.vars` credentials are migrated once into the user-level config.
|
|
78
|
-
## Device lifecycle and recovery
|
|
79
|
-
|
|
80
|
-
- `chat-relay status` shows the signed-in account, readable device name/id, relay URL, access state, online/offline state, last-seen time, and granted scopes without printing secrets.
|
|
81
|
-
- One account can own multiple PCs. Each PC keeps its own agent id and credential, so devices remain independently visible and revocable.
|
|
82
|
-
- Admins can rename a device from the dashboard without changing its agent id or grants.
|
|
83
|
-
- Retiring a device invalidates its current machine credential and disconnects the live agent. That credential cannot reconnect until the device is authorized again.
|
|
84
|
-
- Recovery after retirement does not require copying tokens: run `chat-relay login --force`, complete browser authorization, then run `chat-relay remote`.
|
|
85
|
-
- A different account cannot reclaim another owner's active or retired agent id; a colliding login receives a separate device identity instead.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
## Agent protocol and capabilities
|
|
89
|
-
|
|
90
|
-
Local agents send a lightweight hello handshake when their WebSocket connects. The handshake reports the protocol version, package version, platform/architecture, and the capabilities that are actually enabled on that machine. Feature routing should use the advertised capability list rather than inferring support from the package version alone.
|
|
91
|
-
|
|
92
|
-
Protocol v1 keeps legacy agents backward compatible: a connected agent that does not send hello metadata can still use the pre-handshake behavior. An agent that explicitly advertises an unsupported protocol is disconnected with a clear incompatibility reason instead of failing later on an unrelated tool call or entering a restart loop.
|
|
93
|
-
|
|
94
|
-
## Dashboard administrator sessions
|
|
95
|
-
|
|
96
|
-
Browser dashboard access uses the same Chat Relay username/password accounts but requires an explicit global administrator entitlement. Successful dashboard login creates a short-lived opaque server-side session; the browser receives an HttpOnly, Secure, SameSite=Strict cookie plus a CSRF token for state-changing requests. `ADMIN_TOKEN` remains an operator/CLI recovery credential and must never be embedded in dashboard JavaScript, browser storage, or URLs.
|
|
97
|
-
## Authentication and multi-user model
|
|
98
|
-
|
|
99
|
-
- Browser/device login uses a short-lived device code. Raw user or admin tokens are not typed into the CLI.
|
|
100
|
-
- New accounts use a unique login plus password. Passwords are stored only as salted PBKDF2-SHA256 hashes.
|
|
101
|
-
- Device start/approval requests and failed password attempts are rate-limited.
|
|
102
|
-
- CLI user sessions are opaque random tokens stored server-side only as hashes and expire after 90 days.
|
|
103
|
-
- Re-authentication revokes the previous CLI session when possible.
|
|
104
|
-
- Each local machine has an independent `agentId` and agent token; agent tokens are stored server-side only as hashes.
|
|
105
|
-
- Agent ownership is enforced before an existing machine identity can be reused, preventing shared users from rotating another owner's agent credential.
|
|
106
|
-
- Grants map users to agents with scopes: `read`, `write`, `terminal`, `process`, `desktop_read`, `desktop_control`, or `*`. Explicit legacy grants do not gain desktop access automatically.
|
|
107
|
-
- Logging out revokes the local user session and the owning machine credential.
|
|
108
|
-
- `ADMIN_TOKEN` remains separate and protects administration routes.
|
|
109
|
-
- The existing legacy owner token remains supported only as transitional compatibility while OAuth becomes the normal ChatGPT MCP path.
|
|
110
|
-
|
|
111
|
-
ChatGPT MCP should connect to the plain `/mcp` endpoint. Compatible clients discover OAuth 2.1 automatically, then use authorization-code login with PKCE S256. OAuth access tokens are audience-bound to `/mcp`; `offline_access` issues rotating refresh tokens. Dynamic client registration and protected-resource/authorization-server discovery are exposed for compatible MCP clients. The legacy `/mcp?key=<USER_TOKEN>` flow remains available only during migration. Browser/device login is for the zero-checkout local CLI and creates the same registry user/agent model used by OAuth.
|
|
112
|
-
|
|
113
|
-
## MCP tools
|
|
114
|
-
|
|
115
|
-
Identity and agent routing:
|
|
116
|
-
- `whoami`
|
|
117
|
-
- `list_agents`
|
|
118
|
-
- `ping_agent`
|
|
119
|
-
- `get_config`
|
|
120
|
-
- `get_recent_tool_calls`
|
|
121
|
-
|
|
122
|
-
Filesystem:
|
|
123
|
-
- `stat_path`
|
|
124
|
-
- `list_directory`
|
|
125
|
-
- `read_file` - bounded by bytes; returns `truncated` and `nextOffset` when more lines remain
|
|
126
|
-
- `read_multiple_files` - bounded aggregate response; supports string paths or per-file `{ path, offset, length, maxBytes }` entries plus `maxTotalBytes`
|
|
127
|
-
- `start_search`
|
|
128
|
-
- `get_more_search_results`
|
|
129
|
-
- `write_file`
|
|
130
|
-
- `edit_block`
|
|
131
|
-
- `create_directory`
|
|
132
|
-
- `move_path`
|
|
133
|
-
- `delete_path`
|
|
134
|
-
|
|
135
|
-
Processes:
|
|
136
|
-
- `list_processes`
|
|
137
|
-
- `kill_process`
|
|
138
|
-
|
|
139
|
-
Terminal:
|
|
140
|
-
- `terminal_exec`
|
|
141
|
-
- `terminal_batch_start`
|
|
142
|
-
- `terminal_batch_status`
|
|
143
|
-
- `terminal_batch_read`
|
|
144
|
-
- `terminal_batch_cancel`
|
|
145
|
-
- `terminal_start`
|
|
146
|
-
- `terminal_start_shell`
|
|
147
|
-
- `terminal_read`
|
|
148
|
-
- `terminal_write`
|
|
149
|
-
- `terminal_list`
|
|
150
|
-
- `terminal_kill`
|
|
151
|
-
|
|
152
|
-
Desktop (Windows, opt-in):
|
|
153
|
-
- `screenshot` - returns a bounded MCP image content block plus coordinate metadata
|
|
154
|
-
- `mouse_click` - left/right/middle single or double click in desktop coordinates
|
|
155
|
-
- `keyboard_input` - Unicode text or named key/modifier chord
|
|
156
|
-
|
|
157
|
-
Desktop-Commander-compatible aliases:
|
|
158
|
-
- `start_process`
|
|
159
|
-
- `read_process_output`
|
|
160
|
-
- `interact_with_process`
|
|
161
|
-
- `list_sessions`
|
|
162
|
-
- `force_terminate`
|
|
163
|
-
|
|
164
|
-
## Administration
|
|
165
|
-
|
|
166
|
-
Administration is intentionally separate from MCP. Set the Cloudflare `ADMIN_TOKEN` secret, keep the same value in the local ignored `.dev.vars` on the administrator machine, and use:
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
npm run admin -- state
|
|
170
|
-
npm run admin -- bootstrap default "Primary PC"
|
|
171
|
-
npm run admin -- create-user "Alice"
|
|
172
|
-
npm run admin -- create-agent "Work Laptop" work-laptop
|
|
173
|
-
npm run admin -- grant <userId> <agentId> read,write,terminal,process
|
|
174
|
-
npm run admin -- grant <userId> <agentId> desktop_read
|
|
175
|
-
npm run admin -- grant <userId> <agentId> desktop_read,desktop_control
|
|
176
|
-
npm run admin -- revoke <userId> <agentId>
|
|
177
|
-
npm run admin -- enable-user <userId> false
|
|
178
|
-
$env:CHAT_RELAY_PASSWORD="choose-a-password"; npm run admin -- set-login <userId> <login>
|
|
179
|
-
npm run admin -- enable-agent <agentId> false
|
|
180
|
-
npm run admin -- rotate-user <userId>
|
|
181
|
-
npm run admin -- rotate-agent <agentId>
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Create and rotate commands return the new raw token once. Store it on the corresponding client/agent; the registry retains only its hash.
|
|
185
|
-
|
|
186
|
-
The initial `bootstrap` migrates the legacy `CALLER_TOKEN` and `AGENT_TOKEN` secrets into an owner user and the default agent. To move that existing owner to OAuth without creating a duplicate account, set `CHAT_RELAY_PASSWORD` in the administrator shell and run `npm run admin -- set-login owner <login>`. This attaches login credentials to the same owner user, preserving its grants and agent ownership.
|
|
187
|
-
|
|
188
|
-
## Worker routes
|
|
189
|
-
|
|
190
|
-
| Route | Authentication | Purpose |
|
|
191
|
-
| --- | --- | --- |
|
|
192
|
-
| `GET /health` | none | Worker health |
|
|
193
|
-
| `GET /.well-known/oauth-protected-resource[/mcp]` | none | MCP protected-resource metadata |
|
|
194
|
-
| `GET /.well-known/oauth-authorization-server` | none | OAuth authorization-server metadata |
|
|
195
|
-
| `POST /register` | none | Dynamic registration for public PKCE clients |
|
|
196
|
-
| `GET/POST /authorize` | login/password | OAuth authorization-code sign-in |
|
|
197
|
-
| `POST /token` | public client + PKCE/refresh token | Access/refresh token exchange |
|
|
198
|
-
| `POST /auth/device/start` | none | Start CLI device authorization |
|
|
199
|
-
| `GET /device?user_code=<code>` | none | Browser sign-in/approval page |
|
|
200
|
-
| `POST /auth/device/approve` | login/password + device code | Approve or create a user account |
|
|
201
|
-
| `POST /auth/device/token` | device code | Exchange approved device code for session/agent credentials |
|
|
202
|
-
| `GET /auth/me` | user Bearer token | Current user and permitted agents |
|
|
203
|
-
| `POST /auth/session/revoke` | user Bearer token | Revoke only the current user session |
|
|
204
|
-
| `POST /auth/logout` | user Bearer token | Revoke local session and owning agent credential |
|
|
205
|
-
| `POST /mcp` | OAuth Bearer token | Streamable HTTP MCP (normal path) |
|
|
206
|
-
| `POST /mcp?key=<user token>` | legacy user token | Transitional Streamable HTTP MCP compatibility |
|
|
207
|
-
| `GET /agent?agentId=<id>` | agent Bearer token | Local agent WebSocket |
|
|
208
|
-
| `GET /status?agentId=<id>` | user token | Agent online status |
|
|
209
|
-
| `POST /relay?agentId=<id>` | user token | Direct JSON relay with scope checks |
|
|
210
|
-
| `/admin/*` | admin Bearer token | User/agent/grant administration |
|
|
211
|
-
|
|
212
|
-
##
|
|
213
|
-
|
|
214
|
-
Desktop interaction is disabled by default and requires two independent gates:
|
|
215
|
-
|
|
216
|
-
1. Enable the local agent with `chat-relay remote --desktop` or `DESKTOP_ENABLED=1`. The CLI persists this setting locally. Use `--no-desktop` to disable it again.
|
|
217
|
-
2. Grant `desktop_read` for screenshots and/or `desktop_control` for mouse/keyboard input. Generic read/write/terminal/process scopes do not imply desktop access.
|
|
218
|
-
|
|
219
|
-
- `screenshot` captures the
|
|
220
|
-
- `mouse_click` accepts integer desktop x/y coordinates, button `left|right|middle`, and click count 1 or 2. Invalid/out-of-bounds input is rejected rather than coerced.
|
|
221
|
-
- `keyboard_input` accepts either Unicode `text` or one named `key` with optional Ctrl/Alt/Shift/Win modifiers. Text and key cannot be supplied together.
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
-
|
|
237
|
-
- `
|
|
238
|
-
-
|
|
239
|
-
- `
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
1
|
+
# chat-relay
|
|
2
|
+
|
|
3
|
+
Cloudflare Worker + Durable Objects relay that exposes one or more local Windows or macOS agents to ChatGPT through remote MCP.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
ChatGPT / MCP client
|
|
7
|
+
|
|
|
8
|
+
| Streamable HTTP MCP
|
|
9
|
+
v
|
|
10
|
+
Cloudflare Worker
|
|
11
|
+
|
|
|
12
|
+
+-- Registry Durable Object (users, agents, grants)
|
|
13
|
+
|
|
|
14
|
+
+-- Relay Durable Object per agent
|
|
15
|
+
|
|
|
16
|
+
| WebSocket
|
|
17
|
+
v
|
|
18
|
+
Local Agent
|
|
19
|
+
- terminal
|
|
20
|
+
- files
|
|
21
|
+
- processes
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The relay is generic and has no Devflow-specific logic.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
The CLI is published on npm. You do **not** need to clone this repository or run `npm install`.
|
|
29
|
+
|
|
30
|
+
From any directory:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @anusornneal/chat-relay@latest remote
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
On the first run, Chat Relay opens a browser-based device login. Sign in, approve the computer, then return to the terminal. The CLI saves its credentials in your user profile and connects the local agent automatically.
|
|
37
|
+
|
|
38
|
+
Later, use the same command from anywhere:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @anusornneal/chat-relay@latest remote
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A successful connection looks like:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Chat Relay Remote
|
|
48
|
+
-----------------
|
|
49
|
+
Agent: Primary PC (default)
|
|
50
|
+
Terminal: enabled
|
|
51
|
+
Desktop: disabled
|
|
52
|
+
Agent connected
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Zero-checkout CLI
|
|
56
|
+
|
|
57
|
+
Other commands:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx @anusornneal/chat-relay@latest login
|
|
61
|
+
npx @anusornneal/chat-relay@latest status
|
|
62
|
+
npx @anusornneal/chat-relay@latest logout
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
On Windows the config defaults to `%LOCALAPPDATA%\chat-relay\config.json`. On macOS/Linux it uses `$XDG_CONFIG_HOME/chat-relay/config.json` or `~/.config/chat-relay/config.json`. Set `CHAT_RELAY_HOME` to override the location.
|
|
66
|
+
|
|
67
|
+
Useful options:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
chat-relay remote --root "C:\Users\you\Projects"
|
|
71
|
+
chat-relay login --name "Work PC"
|
|
72
|
+
chat-relay login --no-open
|
|
73
|
+
chat-relay remote --desktop
|
|
74
|
+
chat-relay remote --no-desktop
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The published package locates its bundled local agent relative to the package itself, so commands work from any working directory. Existing repository users can still run `npm start`; legacy `.dev.vars` credentials are migrated once into the user-level config.
|
|
78
|
+
## Device lifecycle and recovery
|
|
79
|
+
|
|
80
|
+
- `chat-relay status` shows the signed-in account, readable device name/id, relay URL, access state, online/offline state, last-seen time, and granted scopes without printing secrets.
|
|
81
|
+
- One account can own multiple PCs. Each PC keeps its own agent id and credential, so devices remain independently visible and revocable.
|
|
82
|
+
- Admins can rename a device from the dashboard without changing its agent id or grants.
|
|
83
|
+
- Retiring a device invalidates its current machine credential and disconnects the live agent. That credential cannot reconnect until the device is authorized again.
|
|
84
|
+
- Recovery after retirement does not require copying tokens: run `chat-relay login --force`, complete browser authorization, then run `chat-relay remote`.
|
|
85
|
+
- A different account cannot reclaim another owner's active or retired agent id; a colliding login receives a separate device identity instead.
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Agent protocol and capabilities
|
|
89
|
+
|
|
90
|
+
Local agents send a lightweight hello handshake when their WebSocket connects. The handshake reports the protocol version, package version, platform/architecture, and the capabilities that are actually enabled on that machine. Feature routing should use the advertised capability list rather than inferring support from the package version alone.
|
|
91
|
+
|
|
92
|
+
Protocol v1 keeps legacy agents backward compatible: a connected agent that does not send hello metadata can still use the pre-handshake behavior. An agent that explicitly advertises an unsupported protocol is disconnected with a clear incompatibility reason instead of failing later on an unrelated tool call or entering a restart loop.
|
|
93
|
+
|
|
94
|
+
## Dashboard administrator sessions
|
|
95
|
+
|
|
96
|
+
Browser dashboard access uses the same Chat Relay username/password accounts but requires an explicit global administrator entitlement. Successful dashboard login creates a short-lived opaque server-side session; the browser receives an HttpOnly, Secure, SameSite=Strict cookie plus a CSRF token for state-changing requests. `ADMIN_TOKEN` remains an operator/CLI recovery credential and must never be embedded in dashboard JavaScript, browser storage, or URLs.
|
|
97
|
+
## Authentication and multi-user model
|
|
98
|
+
|
|
99
|
+
- Browser/device login uses a short-lived device code. Raw user or admin tokens are not typed into the CLI.
|
|
100
|
+
- New accounts use a unique login plus password. Passwords are stored only as salted PBKDF2-SHA256 hashes.
|
|
101
|
+
- Device start/approval requests and failed password attempts are rate-limited.
|
|
102
|
+
- CLI user sessions are opaque random tokens stored server-side only as hashes and expire after 90 days.
|
|
103
|
+
- Re-authentication revokes the previous CLI session when possible.
|
|
104
|
+
- Each local machine has an independent `agentId` and agent token; agent tokens are stored server-side only as hashes.
|
|
105
|
+
- Agent ownership is enforced before an existing machine identity can be reused, preventing shared users from rotating another owner's agent credential.
|
|
106
|
+
- Grants map users to agents with scopes: `read`, `write`, `terminal`, `process`, `desktop_read`, `desktop_control`, or `*`. Explicit legacy grants do not gain desktop access automatically.
|
|
107
|
+
- Logging out revokes the local user session and the owning machine credential.
|
|
108
|
+
- `ADMIN_TOKEN` remains separate and protects administration routes.
|
|
109
|
+
- The existing legacy owner token remains supported only as transitional compatibility while OAuth becomes the normal ChatGPT MCP path.
|
|
110
|
+
|
|
111
|
+
ChatGPT MCP should connect to the plain `/mcp` endpoint. Compatible clients discover OAuth 2.1 automatically, then use authorization-code login with PKCE S256. OAuth access tokens are audience-bound to `/mcp`; `offline_access` issues rotating refresh tokens. Dynamic client registration and protected-resource/authorization-server discovery are exposed for compatible MCP clients. The legacy `/mcp?key=<USER_TOKEN>` flow remains available only during migration. Browser/device login is for the zero-checkout local CLI and creates the same registry user/agent model used by OAuth.
|
|
112
|
+
|
|
113
|
+
## MCP tools
|
|
114
|
+
|
|
115
|
+
Identity and agent routing:
|
|
116
|
+
- `whoami`
|
|
117
|
+
- `list_agents`
|
|
118
|
+
- `ping_agent`
|
|
119
|
+
- `get_config`
|
|
120
|
+
- `get_recent_tool_calls`
|
|
121
|
+
|
|
122
|
+
Filesystem:
|
|
123
|
+
- `stat_path`
|
|
124
|
+
- `list_directory`
|
|
125
|
+
- `read_file` - bounded by bytes; returns `truncated` and `nextOffset` when more lines remain
|
|
126
|
+
- `read_multiple_files` - bounded aggregate response; supports string paths or per-file `{ path, offset, length, maxBytes }` entries plus `maxTotalBytes`
|
|
127
|
+
- `start_search`
|
|
128
|
+
- `get_more_search_results`
|
|
129
|
+
- `write_file`
|
|
130
|
+
- `edit_block`
|
|
131
|
+
- `create_directory`
|
|
132
|
+
- `move_path`
|
|
133
|
+
- `delete_path`
|
|
134
|
+
|
|
135
|
+
Processes:
|
|
136
|
+
- `list_processes`
|
|
137
|
+
- `kill_process`
|
|
138
|
+
|
|
139
|
+
Terminal:
|
|
140
|
+
- `terminal_exec`
|
|
141
|
+
- `terminal_batch_start`
|
|
142
|
+
- `terminal_batch_status`
|
|
143
|
+
- `terminal_batch_read`
|
|
144
|
+
- `terminal_batch_cancel`
|
|
145
|
+
- `terminal_start`
|
|
146
|
+
- `terminal_start_shell`
|
|
147
|
+
- `terminal_read`
|
|
148
|
+
- `terminal_write`
|
|
149
|
+
- `terminal_list`
|
|
150
|
+
- `terminal_kill`
|
|
151
|
+
|
|
152
|
+
Desktop (Windows/macOS, opt-in):
|
|
153
|
+
- `screenshot` - returns a bounded MCP image content block plus coordinate metadata
|
|
154
|
+
- `mouse_click` - left/right/middle single or double click in desktop coordinates
|
|
155
|
+
- `keyboard_input` - Unicode text or named key/modifier chord
|
|
156
|
+
|
|
157
|
+
Desktop-Commander-compatible aliases:
|
|
158
|
+
- `start_process`
|
|
159
|
+
- `read_process_output`
|
|
160
|
+
- `interact_with_process`
|
|
161
|
+
- `list_sessions`
|
|
162
|
+
- `force_terminate`
|
|
163
|
+
|
|
164
|
+
## Administration
|
|
165
|
+
|
|
166
|
+
Administration is intentionally separate from MCP. Set the Cloudflare `ADMIN_TOKEN` secret, keep the same value in the local ignored `.dev.vars` on the administrator machine, and use:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
npm run admin -- state
|
|
170
|
+
npm run admin -- bootstrap default "Primary PC"
|
|
171
|
+
npm run admin -- create-user "Alice"
|
|
172
|
+
npm run admin -- create-agent "Work Laptop" work-laptop
|
|
173
|
+
npm run admin -- grant <userId> <agentId> read,write,terminal,process
|
|
174
|
+
npm run admin -- grant <userId> <agentId> desktop_read
|
|
175
|
+
npm run admin -- grant <userId> <agentId> desktop_read,desktop_control
|
|
176
|
+
npm run admin -- revoke <userId> <agentId>
|
|
177
|
+
npm run admin -- enable-user <userId> false
|
|
178
|
+
$env:CHAT_RELAY_PASSWORD="choose-a-password"; npm run admin -- set-login <userId> <login>
|
|
179
|
+
npm run admin -- enable-agent <agentId> false
|
|
180
|
+
npm run admin -- rotate-user <userId>
|
|
181
|
+
npm run admin -- rotate-agent <agentId>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Create and rotate commands return the new raw token once. Store it on the corresponding client/agent; the registry retains only its hash.
|
|
185
|
+
|
|
186
|
+
The initial `bootstrap` migrates the legacy `CALLER_TOKEN` and `AGENT_TOKEN` secrets into an owner user and the default agent. To move that existing owner to OAuth without creating a duplicate account, set `CHAT_RELAY_PASSWORD` in the administrator shell and run `npm run admin -- set-login owner <login>`. This attaches login credentials to the same owner user, preserving its grants and agent ownership.
|
|
187
|
+
|
|
188
|
+
## Worker routes
|
|
189
|
+
|
|
190
|
+
| Route | Authentication | Purpose |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| `GET /health` | none | Worker health |
|
|
193
|
+
| `GET /.well-known/oauth-protected-resource[/mcp]` | none | MCP protected-resource metadata |
|
|
194
|
+
| `GET /.well-known/oauth-authorization-server` | none | OAuth authorization-server metadata |
|
|
195
|
+
| `POST /register` | none | Dynamic registration for public PKCE clients |
|
|
196
|
+
| `GET/POST /authorize` | login/password | OAuth authorization-code sign-in |
|
|
197
|
+
| `POST /token` | public client + PKCE/refresh token | Access/refresh token exchange |
|
|
198
|
+
| `POST /auth/device/start` | none | Start CLI device authorization |
|
|
199
|
+
| `GET /device?user_code=<code>` | none | Browser sign-in/approval page |
|
|
200
|
+
| `POST /auth/device/approve` | login/password + device code | Approve or create a user account |
|
|
201
|
+
| `POST /auth/device/token` | device code | Exchange approved device code for session/agent credentials |
|
|
202
|
+
| `GET /auth/me` | user Bearer token | Current user and permitted agents |
|
|
203
|
+
| `POST /auth/session/revoke` | user Bearer token | Revoke only the current user session |
|
|
204
|
+
| `POST /auth/logout` | user Bearer token | Revoke local session and owning agent credential |
|
|
205
|
+
| `POST /mcp` | OAuth Bearer token | Streamable HTTP MCP (normal path) |
|
|
206
|
+
| `POST /mcp?key=<user token>` | legacy user token | Transitional Streamable HTTP MCP compatibility |
|
|
207
|
+
| `GET /agent?agentId=<id>` | agent Bearer token | Local agent WebSocket |
|
|
208
|
+
| `GET /status?agentId=<id>` | user token | Agent online status |
|
|
209
|
+
| `POST /relay?agentId=<id>` | user token | Direct JSON relay with scope checks |
|
|
210
|
+
| `/admin/*` | admin Bearer token | User/agent/grant administration |
|
|
211
|
+
|
|
212
|
+
## Desktop access (Windows/macOS, opt-in)
|
|
213
|
+
|
|
214
|
+
Desktop interaction is disabled by default and requires two independent gates:
|
|
215
|
+
|
|
216
|
+
1. Enable the local agent with `chat-relay remote --desktop` or `DESKTOP_ENABLED=1`. The CLI persists this setting locally. Use `--no-desktop` to disable it again.
|
|
217
|
+
2. Grant `desktop_read` for screenshots and/or `desktop_control` for mouse/keyboard input. Generic read/write/terminal/process scopes do not imply desktop access.
|
|
218
|
+
|
|
219
|
+
- `screenshot` captures the selected interactive display on Windows or macOS, scales/compresses it to a bounded JPEG, and returns it as an MCP image block. The accompanying metadata contains image size, desktop origin/size, and scale factors for converting screenshot pixels to desktop coordinates.
|
|
220
|
+
- `mouse_click` accepts integer desktop x/y coordinates, button `left|right|middle`, and click count 1 or 2. Invalid/out-of-bounds input is rejected rather than coerced.
|
|
221
|
+
- `keyboard_input` accepts either Unicode `text` or one named `key` with optional Ctrl/Alt/Shift/Win modifiers. Text and key cannot be supplied together.
|
|
222
|
+
- `desktop_step` batches 1-20 actions in one local round trip, compacts adjacent text/waits, and uses adaptive settle delays. It does not capture by default; set `captureAfter: true` when a post-action screenshot is required. Timing metadata separates local queue, input, wait/settle, capture, and encode costs.
|
|
223
|
+
- Windows uses the persistent PowerShell desktop worker. macOS uses built-in `screencapture`, `sips`, `osascript`/JXA, CoreGraphics, and System Events; no extra npm/native dependency is required.
|
|
224
|
+
- macOS desktop capture requires Screen Recording permission for the terminal/Node process, while mouse, keyboard, and window control require Accessibility permission. Missing permissions return controlled errors instead of crashing the reconnect loop.
|
|
225
|
+
- Linux desktop control is not implemented yet and returns `unsupported_platform`.
|
|
226
|
+
- Screenshot bytes, typed text, key chords, and click coordinates are not stored in recentCalls. Only action/timing/success metadata is retained there.
|
|
227
|
+
- This card does not add streaming video, OCR, remote-desktop viewer UI, clipboard sync, drag-and-drop, app-specific automation, or Session 0/service automation.
|
|
228
|
+
|
|
229
|
+
## Public plugin review
|
|
230
|
+
|
|
231
|
+
Public ChatGPT onboarding uses the plain production `/mcp` URL and OAuth; the legacy `?key=` route is migration compatibility only. Submission/reviewer requirements, permission boundaries, privacy/retention behavior, domain-verification setup, positive/negative test cases, and clean-room demo steps are maintained in `docs/plugin-review.md`.
|
|
232
|
+
|
|
233
|
+
The OpenAI domain verification token is served at `/.well-known/openai-apps-challenge` when `OPENAI_APPS_CHALLENGE` is configured. Keep reviewer credentials and challenge values separate from production administrator secrets.
|
|
234
|
+
## Runtime limits and controls
|
|
235
|
+
|
|
236
|
+
- Per-user durable rate/quota controls are available before local-agent dispatch, but both are disabled by default (`rateLimit=0`, `dailyCallQuota=0`). Set `USER_RATE_LIMIT_PER_WINDOW` and/or `USER_DAILY_CALL_QUOTA` to a positive value to enable them; `USER_RATE_WINDOW_SECONDS` controls the rate window (1-3600 seconds).
|
|
237
|
+
- Admins can inspect or override the effective policy with `GET/POST /admin/api/limits`; POST `{ "resetToDefaults": true }` returns to environment defaults. Rejections return HTTP 429 with `rate_limited` or `quota_exceeded`, `Retry-After`, and reset metadata, and are recorded as bounded usage events without dispatching agent work.
|
|
238
|
+
- Relay request/response message: 64 KiB. The local agent caps serialized responses below that transport ceiling and returns `response_too_large` instead of allowing a silent timeout.
|
|
239
|
+
- `read_file` defaults to a 32 KiB content budget (max 48 KiB) and exposes deterministic `nextOffset` continuation.
|
|
240
|
+
- `read_multiple_files` defaults to a 48 KiB aggregate budget (max 48 KiB). If not all requested files fit, use `nextIndex`; if an individual file is truncated, continue it with that entry's `nextOffset`.
|
|
241
|
+
- One-shot terminal command: max 20 seconds.
|
|
242
|
+
- `terminal_batch_start` accepts 2-20 jobs in one MCP call to avoid N-call E2E dispatch overhead. Each agent owns an independent FIFO queue and bounded execution pool.
|
|
243
|
+
- Batch concurrency defaults to 4 and is capped at 8 per agent. Override with `TERMINAL_BATCH_CONCURRENCY`; queued jobs default to 64 and are capped at 256 via `TERMINAL_BATCH_MAX_QUEUED`.
|
|
244
|
+
- Queue overflow fails fast with `queue_full` instead of spawning unbounded processes. Use `terminal_batch_status`, `terminal_batch_read`, and `terminal_batch_cancel` for lifecycle control.
|
|
245
|
+
- Batch output retained in memory is capped at 8 KiB per job; completed batches are capped at 64 per agent and also expire after 30 minutes.
|
|
246
|
+
- Scaling is horizontal by agent: each connected machine has its own queue/concurrency budget, so additional agents add execution capacity without sharing one local hot queue.
|
|
247
|
+
- Persistent terminal sessions: up to 8 running sessions per local agent.
|
|
248
|
+
- Terminal output buffer: bounded in memory; completed sessions retained for 30 minutes.
|
|
249
|
+
- Filesystem reads/writes are bounded and restricted to configured `ALLOWED_ROOTS`.
|
|
250
|
+
- Search skips common heavy directories such as `.git`, `node_modules`, `.gradle`, `.idea`, and `.wrangler`.
|
|
251
|
+
- The agent blocks a small set of high-risk system-management commands. This is defense in depth, not a security sandbox.
|
|
252
|
+
|
|
253
|
+
## Audit, retention, and recovery
|
|
254
|
+
|
|
255
|
+
- Security/admin mutations are recorded in a dedicated `Audit` Durable Object with actor, action, target, result, and sanitized scalar metadata only. Passwords, tokens, cookies, CSRF values, commands, file content, and raw tool payloads are excluded.
|
|
256
|
+
- Raw usage events default to 30-day retention while daily usage aggregates are preserved independently. Set `USAGE_RAW_RETENTION_DAYS` to change raw-event retention.
|
|
257
|
+
- Audit events default to 180-day retention. Set `AUDIT_RETENTION_DAYS` to change that window.
|
|
258
|
+
- `GET /admin/api/audit` returns bounded audit history. `GET /admin/api/operations` exposes component health and last cleanup state. `POST /admin/api/operations/cleanup` performs bounded idempotent cleanup across registry auth transients, raw usage events, and audit history.
|
|
259
|
+
- Cleanup does not intentionally remove active users, grants, agents, live sessions, pending non-expired device authorization, or long-lived usage aggregates.
|
|
260
|
+
- Durable Object state is the production source of truth. Git/npm artifacts do not back it up. Before destructive migration or account transfer, export any operator-required identity/config state separately and treat Cloudflare account/Durable Object recovery controls as the infrastructure recovery boundary.
|
|
261
|
+
- Cleanup failures are surfaced through the operations endpoint and audit result instead of being silently treated as success.
|
|
262
|
+
## Tests
|
|
263
|
+
|
|
264
|
+
The legacy relay integration test remains available:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
npm run dev:test
|
|
268
|
+
python test/integration.py
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The full MCP/multi-user smoke test expects a local Worker on port 8795 plus an attached local agent:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
node test/multiuser-smoke.mjs
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
It verifies the MCP tool surface, agent routing, filesystem operations, search sessions, process listing, persistent terminals, scope enforcement, multi-agent selection, and token rotation.
|
|
278
|
+
|
|
279
|
+
Device/browser login is covered by:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
TEST_RELAY_URL=http://127.0.0.1:8796 npm run test:device-auth
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
OAuth discovery, DCR, PKCE, token exchange, refresh rotation, audience binding, MCP Bearer auth, and legacy-key compatibility are covered by:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
TEST_RELAY_URL=http://127.0.0.1:8796 npm run test:oauth
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Per-user rate/quota enforcement, isolation, reset-window behavior, disabled policy, rejection observability, and pre-dispatch blocking are covered by:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
TEST_RELAY_URL=http://127.0.0.1:8804 npm run test:quota
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Desktop tool contracts, scope separation, disabled/unsupported gates, controlled image mapping, and input validation are covered by:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
TEST_RELAY_URL=http://127.0.0.1:8807 npm run test:desktop
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
After `npm pack`, the zero-checkout package smoke test installs and runs the tarball from a temporary directory:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
CHAT_RELAY_TARBALL=<path-to-tgz> TEST_RELAY_URL=http://127.0.0.1:8796 npm run test:zero-checkout
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The npm package is configured as `@anusornneal/chat-relay`. Publishing requires an authenticated npm account with access to that scope.
|
|
310
|
+
|
|
311
|
+
## CI and npm publishing
|
|
312
|
+
|
|
313
|
+
GitHub Actions runs package verification on Node 20 and Node 24. A separate integration job starts a local Worker and verifies device auth, OAuth, and the zero-checkout tarball flow.
|
|
314
|
+
|
|
315
|
+
Before publishing locally:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
npm run verify:publish
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
This checks the npm file set, CLI entrypoint, Worker dry-run build, and production dependency audit. The package whitelist excludes .dev.vars, Worker source, tests, and repository-only files.
|
|
322
|
+
|
|
323
|
+
The repository also includes a Publish npm workflow for version tags or manual dispatch. It supports npm trusted publishing through GitHub OIDC and can also use an NPM_TOKEN repository secret when configured.
|
|
324
|
+
|
|
325
|
+
The first registry publish still requires npm authorization for the @anusornneal scope. After publishing, verify the exact public UX from a clean directory:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npx @anusornneal/chat-relay@latest status
|
|
329
|
+
npx @anusornneal/chat-relay@latest remote
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
### Safe local-agent restart
|
|
334
|
+
|
|
335
|
+
Agent updates remain explicit and user-controlled. Check the current agent version, protocol compatibility, and lifecycle with `chat-relay status`.
|
|
336
|
+
|
|
337
|
+
Before restarting an active remote, run `chat-relay drain`. Drain mode rejects new long-running work while existing terminal/session control remains available so bounded work can finish or be stopped safely. When `status` reports the lifecycle is ready to restart, run `chat-relay restart`. Use `chat-relay resume` to cancel a drain before restart.
|
|
338
|
+
|
|
339
|
+
To install a newer package, stop/restart the wrapper with the desired npm version (for example `npx @anusornneal/chat-relay@latest remote --desktop`). The agent does not self-modify or automatically cross an incompatible protocol version.
|
|
337
340
|
|
|
338
341
|
|
|
339
342
|
### Trusted-team security baseline
|