quoroom 0.1.8 → 0.1.11
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 +53 -12
- package/out/mcp/api-server.js +68 -13
- package/out/mcp/cli.js +70 -15
- package/out/mcp/server.js +1 -1
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://www.npmjs.com/package/quoroom)
|
|
11
|
-
[](#)
|
|
12
12
|
[](https://github.com/quoroom-ai/room/stargazers)
|
|
13
13
|
[](https://github.com/quoroom-ai/room/releases/latest)
|
|
14
14
|
[](https://github.com/quoroom-ai/room/releases/latest)
|
|
@@ -38,6 +38,7 @@ Official channels only:
|
|
|
38
38
|
|
|
39
39
|
- `https://quoroom.ai`
|
|
40
40
|
- `https://github.com/quoroom-ai`
|
|
41
|
+
- Telegram: `@quoroom_ai_bot`
|
|
41
42
|
|
|
42
43
|
If you see impersonation or scam activity, report it to `hello@quoroom.ai`.
|
|
43
44
|
See `TRADEMARKS.md` for full trademark usage terms.
|
|
@@ -108,6 +109,12 @@ Quoroom is an open research project exploring autonomous agent collectives. Each
|
|
|
108
109
|
|
|
109
110
|
**Dashboard** — React SPA served directly by your local Quoroom server at `http://localhost:3700` (or your configured port). Manage rooms, agents, goals, memory, wallet — all from the browser, with local-first data storage.
|
|
110
111
|
|
|
112
|
+
**Cloud Mode** — Deploy to the cloud and control your room remotely. Same dashboard works in both local and cloud mode. Cloud instances auto-detect their environment, support JWT-based auth, and serve the UI over HTTPS with strict CORS. Connect your Claude or Codex subscription from the remote Settings panel.
|
|
113
|
+
|
|
114
|
+
**Inbox** — Rooms can message the keeper and other rooms. Cross-room communication with reply threading. Agents escalate decisions, share updates, or request resources from neighboring rooms.
|
|
115
|
+
|
|
116
|
+
**Credentials** — Secure credential storage for API keys and secrets. Agents list and retrieve credentials at runtime without exposing raw values in prompts or logs.
|
|
117
|
+
|
|
111
118
|
**Auto-updates** — The server polls GitHub for new releases every 4 hours. When a new version is available, the dashboard shows a notification popup and a download row in Settings. One click downloads the installer for your platform directly — no browser redirect.
|
|
112
119
|
|
|
113
120
|
---
|
|
@@ -213,6 +220,7 @@ The room engine exposes an MCP server over stdio. All tools use the `quoroom_` p
|
|
|
213
220
|
| `quoroom_pause_room` | Pause a running room |
|
|
214
221
|
| `quoroom_restart_room` | Restart a paused room |
|
|
215
222
|
| `quoroom_delete_room` | Delete a room |
|
|
223
|
+
| `quoroom_configure_room` | Update room configuration |
|
|
216
224
|
|
|
217
225
|
### Quorum
|
|
218
226
|
|
|
@@ -304,6 +312,7 @@ The room engine exposes an MCP server over stdio. All tools use the `quoroom_` p
|
|
|
304
312
|
| `quoroom_wallet_balance` | Check on-chain balance (USDC/USDT, all chains) |
|
|
305
313
|
| `quoroom_wallet_send` | Send USDC or USDT on any supported chain |
|
|
306
314
|
| `quoroom_wallet_history` | View transaction history |
|
|
315
|
+
| `quoroom_wallet_topup` | Get wallet top-up URL |
|
|
307
316
|
|
|
308
317
|
### Identity
|
|
309
318
|
|
|
@@ -324,9 +333,32 @@ The room engine exposes an MCP server over stdio. All tools use the `quoroom_` p
|
|
|
324
333
|
| `quoroom_station_stop` | Stop a running station |
|
|
325
334
|
| `quoroom_station_exec` | Execute a command on a station |
|
|
326
335
|
| `quoroom_station_logs` | View station logs |
|
|
327
|
-
| `quoroom_station_deploy` | Deploy to a station |
|
|
328
|
-
| `quoroom_station_domain` | Manage station domain |
|
|
329
336
|
| `quoroom_station_delete` | Delete a station |
|
|
337
|
+
| `quoroom_station_cancel` | Cancel a pending station |
|
|
338
|
+
| `quoroom_station_create_crypto` | Provision a station with crypto payment |
|
|
339
|
+
| `quoroom_station_renew_crypto` | Renew a station with crypto payment |
|
|
340
|
+
|
|
341
|
+
### Inbox
|
|
342
|
+
|
|
343
|
+
| Tool | Description |
|
|
344
|
+
|------|-------------|
|
|
345
|
+
| `quoroom_inbox_send_keeper` | Send a message to the keeper |
|
|
346
|
+
| `quoroom_inbox_send_room` | Send a message to another room |
|
|
347
|
+
| `quoroom_inbox_list` | List inbox messages |
|
|
348
|
+
| `quoroom_inbox_reply` | Reply to a room message |
|
|
349
|
+
|
|
350
|
+
### Credentials
|
|
351
|
+
|
|
352
|
+
| Tool | Description |
|
|
353
|
+
|------|-------------|
|
|
354
|
+
| `quoroom_credentials_list` | List stored credentials |
|
|
355
|
+
| `quoroom_credentials_get` | Get a credential value |
|
|
356
|
+
|
|
357
|
+
### Resources
|
|
358
|
+
|
|
359
|
+
| Tool | Description |
|
|
360
|
+
|------|-------------|
|
|
361
|
+
| `quoroom_resources_get` | Get local system resources (CPU, memory, disk) |
|
|
330
362
|
|
|
331
363
|
### Settings
|
|
332
364
|
|
|
@@ -351,6 +383,13 @@ npm run test:watch # Watch mode
|
|
|
351
383
|
npm run test:e2e # End-to-end tests (Playwright)
|
|
352
384
|
```
|
|
353
385
|
|
|
386
|
+
### Docker (cloud runtime)
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
docker build -t quoroom .
|
|
390
|
+
docker run -p 3700:3700 quoroom
|
|
391
|
+
```
|
|
392
|
+
|
|
354
393
|
## Releasing
|
|
355
394
|
|
|
356
395
|
Use the release runbook: [`docs/RELEASE_RUNBOOK.md`](docs/RELEASE_RUNBOOK.md)
|
|
@@ -365,19 +404,19 @@ room/
|
|
|
365
404
|
│ ├── mcp/ # MCP server (stdio)
|
|
366
405
|
│ │ ├── server.ts # Tool registration
|
|
367
406
|
│ │ ├── db.ts # Database initialization
|
|
368
|
-
│ │ └── tools/ #
|
|
407
|
+
│ │ └── tools/ # 17 tool modules
|
|
369
408
|
│ ├── server/ # HTTP/WebSocket API server
|
|
370
|
-
│ │ ├── index.ts # Server bootstrap
|
|
409
|
+
│ │ ├── index.ts # Server bootstrap (local + cloud mode)
|
|
371
410
|
│ │ ├── router.ts # Request router
|
|
372
|
-
│ │ ├── auth.ts # Dual-token auth + CORS
|
|
411
|
+
│ │ ├── auth.ts # Dual-token auth + CORS + cloud JWT
|
|
373
412
|
│ │ ├── access.ts # Role-based access control
|
|
374
413
|
│ │ ├── ws.ts # WebSocket real-time events
|
|
375
|
-
│ │ └── routes/ # REST API routes
|
|
414
|
+
│ │ └── routes/ # REST API routes (20 modules)
|
|
376
415
|
│ ├── ui/ # React SPA dashboard
|
|
377
416
|
│ │ ├── App.tsx # Root component
|
|
378
|
-
│ │ ├── components/ # UI components
|
|
417
|
+
│ │ ├── components/ # UI components (32 modules)
|
|
379
418
|
│ │ ├── hooks/ # React hooks
|
|
380
|
-
│ │ └── lib/ # API client, auth, WebSocket
|
|
419
|
+
│ │ └── lib/ # API client, auth, storage, WebSocket
|
|
381
420
|
│ └── shared/ # Core engine
|
|
382
421
|
│ ├── agent-loop.ts # Worker agent loop with rate limiting
|
|
383
422
|
│ ├── agent-executor.ts # Claude Code CLI execution
|
|
@@ -389,15 +428,17 @@ room/
|
|
|
389
428
|
│ ├── identity.ts # ERC-8004 on-chain identity
|
|
390
429
|
│ ├── station.ts # Cloud provisioning
|
|
391
430
|
│ ├── task-runner.ts # Task execution engine
|
|
431
|
+
│ ├── model-provider.ts # Multi-provider LLM support
|
|
432
|
+
│ ├── cloud-sync.ts # Cloud registration + heartbeat
|
|
392
433
|
│ ├── db-queries.ts # Database query layer
|
|
393
434
|
│ ├── schema.ts # SQLite schema (WAL mode)
|
|
394
435
|
│ ├── embeddings.ts # Vector embeddings (all-MiniLM-L6-v2)
|
|
395
|
-
│
|
|
396
|
-
│ └── __tests__/ # Test suite
|
|
436
|
+
│ └── __tests__/ # Test suite (907 tests)
|
|
397
437
|
├── e2e/ # Playwright end-to-end tests
|
|
398
438
|
├── scripts/
|
|
399
439
|
│ └── build-mcp.js # esbuild bundling
|
|
400
|
-
|
|
440
|
+
├── Dockerfile # Cloud runtime image
|
|
441
|
+
└── docs/ # Media assets + architecture docs
|
|
401
442
|
```
|
|
402
443
|
|
|
403
444
|
**Tech stack**: TypeScript (strict), React, Tailwind CSS, better-sqlite3, sqlite-vec, viem, MCP SDK, HuggingFace Transformers, node-cron, zod, esbuild, Vite, Vitest
|
package/out/mcp/api-server.js
CHANGED
|
@@ -9382,7 +9382,7 @@ var require_package = __commonJS({
|
|
|
9382
9382
|
"package.json"(exports2, module2) {
|
|
9383
9383
|
module2.exports = {
|
|
9384
9384
|
name: "quoroom",
|
|
9385
|
-
version: "0.1.
|
|
9385
|
+
version: "0.1.11",
|
|
9386
9386
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
9387
9387
|
main: "./out/mcp/server.js",
|
|
9388
9388
|
bin: {
|
|
@@ -9404,16 +9404,28 @@ var require_package = __commonJS({
|
|
|
9404
9404
|
build: "npm run typecheck && npm run build:mcp && npm run build:ui",
|
|
9405
9405
|
"build:mcp": "node scripts/build-mcp.js",
|
|
9406
9406
|
"build:ui": "vite build --config src/ui/vite.config.ts",
|
|
9407
|
-
|
|
9408
|
-
"dev
|
|
9409
|
-
"dev:
|
|
9407
|
+
"kill:ports": "node scripts/kill-ports.js",
|
|
9408
|
+
"kill:dev-ports": "npm run kill:ports -- 4700 3710",
|
|
9409
|
+
"dev:links": "node scripts/dev-links.js",
|
|
9410
|
+
dev: `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
|
|
9411
|
+
"dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
9412
|
+
"dev:room:isolated": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
9413
|
+
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
9414
|
+
"doctor:split": "node scripts/doctor-split.js",
|
|
9415
|
+
"dev:isolated": `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & wait'`,
|
|
9416
|
+
"dev:cloud": `sh -c 'npm run kill:ports -- 3710 && cd ../cloud && PORT=3710 CLOUD_PUBLIC_URL=http://127.0.0.1:3710 CLOUD_ALLOWED_ORIGINS='"'"'http://127.0.0.1:3710,http://localhost:3710,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'"'"' npm start'`,
|
|
9410
9417
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
9411
9418
|
"seed:style-demo": "node scripts/seed-style-demo.js",
|
|
9412
9419
|
typecheck: "tsc --noEmit",
|
|
9413
9420
|
test: "npm run rebuild:native:node && vitest run --pool=forks --passWithNoTests",
|
|
9414
9421
|
"test:watch": "npm run rebuild:native:node && vitest --pool=forks",
|
|
9422
|
+
"test:quick": "npm run typecheck && npm run test",
|
|
9423
|
+
"test:smart-e2e": "git diff --cached --name-only | grep -qE '^(src/ui/|src/server/|e2e/|playwright)' && npm run test:e2e || echo 'No UI/server changes \u2014 skipping E2E'",
|
|
9415
9424
|
"test:e2e": "npm run build && npx playwright test",
|
|
9416
9425
|
"test:e2e:ui": "npm run build && npx playwright test e2e/ui.test.ts",
|
|
9426
|
+
"test:e2e:setup": "npm run build && npx playwright test e2e/setup-flow.test.ts",
|
|
9427
|
+
"test:e2e:setup:headed": "npm run build && npx playwright test e2e/setup-flow.test.ts --headed --project=chromium",
|
|
9428
|
+
"test:e2e:providers": "npm run build && npx playwright test e2e/provider-flows.test.ts",
|
|
9417
9429
|
"rebuild:native:node": `node -e "try{require('better-sqlite3')(':memory:').close()}catch{process.exit(1)}" || npx --yes node-gyp rebuild --directory=node_modules/better-sqlite3`,
|
|
9418
9430
|
prepublishOnly: "npm run build:mcp"
|
|
9419
9431
|
},
|
|
@@ -10453,9 +10465,6 @@ function getCloudAllowedOrigins() {
|
|
|
10453
10465
|
const origins = configured.length > 0 ? configured : DEFAULT_CLOUD_ALLOWED_ORIGINS;
|
|
10454
10466
|
return new Set(origins.map(normalizeOrigin).filter(Boolean));
|
|
10455
10467
|
}
|
|
10456
|
-
function getCloudUserToken() {
|
|
10457
|
-
return (process.env.QUOROOM_CLOUD_USER_TOKEN || "").trim();
|
|
10458
|
-
}
|
|
10459
10468
|
function getCloudJwtSecret() {
|
|
10460
10469
|
return (process.env.QUOROOM_CLOUD_JWT_SECRET || "").trim();
|
|
10461
10470
|
}
|
|
@@ -10554,9 +10563,9 @@ function generateToken(dataDir) {
|
|
|
10554
10563
|
return agentToken;
|
|
10555
10564
|
}
|
|
10556
10565
|
}
|
|
10557
|
-
agentToken = dataDir ? readLegacyAgentToken(dataDir) ?? import_node_crypto.default.randomBytes(32).toString("hex") : import_node_crypto.default.randomBytes(32).toString("hex");
|
|
10566
|
+
agentToken = dataDir && !isCloudDeployment() ? readLegacyAgentToken(dataDir) ?? import_node_crypto.default.randomBytes(32).toString("hex") : import_node_crypto.default.randomBytes(32).toString("hex");
|
|
10558
10567
|
userToken = import_node_crypto.default.randomBytes(32).toString("hex");
|
|
10559
|
-
if (dataDir) writePersistedTokens(dataDir, agentToken, userToken);
|
|
10568
|
+
if (dataDir && !isCloudDeployment()) writePersistedTokens(dataDir, agentToken, userToken);
|
|
10560
10569
|
return agentToken;
|
|
10561
10570
|
}
|
|
10562
10571
|
function getUserToken() {
|
|
@@ -10572,10 +10581,10 @@ function validateToken(authHeader) {
|
|
|
10572
10581
|
const cloudRole = validateCloudJwt(provided);
|
|
10573
10582
|
if (cloudRole) return cloudRole;
|
|
10574
10583
|
}
|
|
10575
|
-
if (isCloudDeployment() && tokenEquals(getCloudUserToken(), provided)) return "user";
|
|
10576
10584
|
return null;
|
|
10577
10585
|
}
|
|
10578
10586
|
function writeTokenFile(dataDir, token, port) {
|
|
10587
|
+
if (isCloudDeployment()) return;
|
|
10579
10588
|
(0, import_node_fs.mkdirSync)(dataDir, { recursive: true });
|
|
10580
10589
|
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(dataDir, "api.token"), token, { mode: 384 });
|
|
10581
10590
|
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(dataDir, "api.port"), String(port));
|
|
@@ -26026,7 +26035,7 @@ var cachedVersion = null;
|
|
|
26026
26035
|
function getVersion3() {
|
|
26027
26036
|
if (cachedVersion) return cachedVersion;
|
|
26028
26037
|
try {
|
|
26029
|
-
cachedVersion =
|
|
26038
|
+
cachedVersion = true ? "0.1.11" : null.version;
|
|
26030
26039
|
} catch {
|
|
26031
26040
|
cachedVersion = "unknown";
|
|
26032
26041
|
}
|
|
@@ -26227,12 +26236,12 @@ function registerStatusRoutes(router) {
|
|
|
26227
26236
|
const ollama = await checkOllama();
|
|
26228
26237
|
const resources = getResources();
|
|
26229
26238
|
const deploymentMode = getDeploymentMode();
|
|
26239
|
+
const isCloud = deploymentMode === "cloud";
|
|
26230
26240
|
return {
|
|
26231
26241
|
data: {
|
|
26232
26242
|
version: getVersion3(),
|
|
26233
26243
|
uptime: Math.floor((Date.now() - startedAt) / 1e3),
|
|
26234
|
-
dataDir,
|
|
26235
|
-
dbPath,
|
|
26244
|
+
...!isCloud && { dataDir, dbPath },
|
|
26236
26245
|
claude,
|
|
26237
26246
|
codex,
|
|
26238
26247
|
ollama,
|
|
@@ -27745,6 +27754,39 @@ function serveStatic(staticDir, pathname, res) {
|
|
|
27745
27754
|
res.end("Not Found");
|
|
27746
27755
|
}
|
|
27747
27756
|
}
|
|
27757
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
27758
|
+
var RATE_LIMIT_READ = 300;
|
|
27759
|
+
var RATE_LIMIT_WRITE = 120;
|
|
27760
|
+
var rateBuckets = /* @__PURE__ */ new Map();
|
|
27761
|
+
setInterval(() => {
|
|
27762
|
+
const now = Date.now();
|
|
27763
|
+
for (const [key, bucket] of rateBuckets) {
|
|
27764
|
+
if (now >= bucket.resetAt) rateBuckets.delete(key);
|
|
27765
|
+
}
|
|
27766
|
+
}, 12e4).unref();
|
|
27767
|
+
function checkRateLimit2(ip, method) {
|
|
27768
|
+
const isWrite = method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
|
|
27769
|
+
const limit = isWrite ? RATE_LIMIT_WRITE : RATE_LIMIT_READ;
|
|
27770
|
+
const key = `${ip}:${isWrite ? "w" : "r"}`;
|
|
27771
|
+
const now = Date.now();
|
|
27772
|
+
let bucket = rateBuckets.get(key);
|
|
27773
|
+
if (!bucket || now >= bucket.resetAt) {
|
|
27774
|
+
bucket = { count: 0, resetAt: now + RATE_LIMIT_WINDOW_MS };
|
|
27775
|
+
rateBuckets.set(key, bucket);
|
|
27776
|
+
}
|
|
27777
|
+
bucket.count++;
|
|
27778
|
+
if (bucket.count > limit) {
|
|
27779
|
+
const retryAfter = Math.ceil((bucket.resetAt - now) / 1e3);
|
|
27780
|
+
return { allowed: false, retryAfter };
|
|
27781
|
+
}
|
|
27782
|
+
return { allowed: true, retryAfter: 0 };
|
|
27783
|
+
}
|
|
27784
|
+
var CLOUD_SECURITY_HEADERS = {
|
|
27785
|
+
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
|
|
27786
|
+
"X-Content-Type-Options": "nosniff",
|
|
27787
|
+
"X-Frame-Options": "DENY",
|
|
27788
|
+
"Referrer-Policy": "strict-origin-when-cross-origin"
|
|
27789
|
+
};
|
|
27748
27790
|
function createApiServer(options = {}) {
|
|
27749
27791
|
const db2 = options.db ?? getServerDatabase();
|
|
27750
27792
|
const port = options.port ?? DEFAULT_PORT;
|
|
@@ -27769,7 +27811,20 @@ function createApiServer(options = {}) {
|
|
|
27769
27811
|
const responseHeaders = {
|
|
27770
27812
|
"Content-Type": "application/json"
|
|
27771
27813
|
};
|
|
27814
|
+
if (isCloudDeployment()) {
|
|
27815
|
+
Object.assign(responseHeaders, CLOUD_SECURITY_HEADERS);
|
|
27816
|
+
}
|
|
27772
27817
|
setCorsHeaders(origin, responseHeaders);
|
|
27818
|
+
if (isCloudDeployment() && pathname.startsWith("/api/")) {
|
|
27819
|
+
const clientIp = req.socket.remoteAddress || "unknown";
|
|
27820
|
+
const rl = checkRateLimit2(clientIp, req.method || "GET");
|
|
27821
|
+
if (!rl.allowed) {
|
|
27822
|
+
responseHeaders["Retry-After"] = String(rl.retryAfter);
|
|
27823
|
+
res.writeHead(429, responseHeaders);
|
|
27824
|
+
res.end(JSON.stringify({ error: "Too many requests" }));
|
|
27825
|
+
return;
|
|
27826
|
+
}
|
|
27827
|
+
}
|
|
27773
27828
|
const isSameOrigin = origin && req.headers.host && (() => {
|
|
27774
27829
|
try {
|
|
27775
27830
|
return new import_node_url.URL(origin).host === req.headers.host;
|
package/out/mcp/cli.js
CHANGED
|
@@ -49800,7 +49800,7 @@ var server_exports = {};
|
|
|
49800
49800
|
async function main() {
|
|
49801
49801
|
const server = new McpServer({
|
|
49802
49802
|
name: "quoroom",
|
|
49803
|
-
version: true ? "0.1.
|
|
49803
|
+
version: true ? "0.1.11" : "0.0.0"
|
|
49804
49804
|
});
|
|
49805
49805
|
registerMemoryTools(server);
|
|
49806
49806
|
registerSchedulerTools(server);
|
|
@@ -49934,9 +49934,6 @@ function getCloudAllowedOrigins() {
|
|
|
49934
49934
|
const origins = configured.length > 0 ? configured : DEFAULT_CLOUD_ALLOWED_ORIGINS;
|
|
49935
49935
|
return new Set(origins.map(normalizeOrigin).filter(Boolean));
|
|
49936
49936
|
}
|
|
49937
|
-
function getCloudUserToken() {
|
|
49938
|
-
return (process.env.QUOROOM_CLOUD_USER_TOKEN || "").trim();
|
|
49939
|
-
}
|
|
49940
49937
|
function getCloudJwtSecret() {
|
|
49941
49938
|
return (process.env.QUOROOM_CLOUD_JWT_SECRET || "").trim();
|
|
49942
49939
|
}
|
|
@@ -50035,9 +50032,9 @@ function generateToken(dataDir) {
|
|
|
50035
50032
|
return agentToken;
|
|
50036
50033
|
}
|
|
50037
50034
|
}
|
|
50038
|
-
agentToken = dataDir ? readLegacyAgentToken(dataDir) ?? import_node_crypto2.default.randomBytes(32).toString("hex") : import_node_crypto2.default.randomBytes(32).toString("hex");
|
|
50035
|
+
agentToken = dataDir && !isCloudDeployment() ? readLegacyAgentToken(dataDir) ?? import_node_crypto2.default.randomBytes(32).toString("hex") : import_node_crypto2.default.randomBytes(32).toString("hex");
|
|
50039
50036
|
userToken = import_node_crypto2.default.randomBytes(32).toString("hex");
|
|
50040
|
-
if (dataDir) writePersistedTokens(dataDir, agentToken, userToken);
|
|
50037
|
+
if (dataDir && !isCloudDeployment()) writePersistedTokens(dataDir, agentToken, userToken);
|
|
50041
50038
|
return agentToken;
|
|
50042
50039
|
}
|
|
50043
50040
|
function getUserToken() {
|
|
@@ -50053,10 +50050,10 @@ function validateToken(authHeader) {
|
|
|
50053
50050
|
const cloudRole = validateCloudJwt(provided);
|
|
50054
50051
|
if (cloudRole) return cloudRole;
|
|
50055
50052
|
}
|
|
50056
|
-
if (isCloudDeployment() && tokenEquals(getCloudUserToken(), provided)) return "user";
|
|
50057
50053
|
return null;
|
|
50058
50054
|
}
|
|
50059
50055
|
function writeTokenFile(dataDir, token, port) {
|
|
50056
|
+
if (isCloudDeployment()) return;
|
|
50060
50057
|
(0, import_node_fs.mkdirSync)(dataDir, { recursive: true });
|
|
50061
50058
|
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(dataDir, "api.token"), token, { mode: 384 });
|
|
50062
50059
|
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(dataDir, "api.port"), String(port));
|
|
@@ -50744,7 +50741,7 @@ var require_package = __commonJS({
|
|
|
50744
50741
|
"package.json"(exports2, module2) {
|
|
50745
50742
|
module2.exports = {
|
|
50746
50743
|
name: "quoroom",
|
|
50747
|
-
version: "0.1.
|
|
50744
|
+
version: "0.1.11",
|
|
50748
50745
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
50749
50746
|
main: "./out/mcp/server.js",
|
|
50750
50747
|
bin: {
|
|
@@ -50766,16 +50763,28 @@ var require_package = __commonJS({
|
|
|
50766
50763
|
build: "npm run typecheck && npm run build:mcp && npm run build:ui",
|
|
50767
50764
|
"build:mcp": "node scripts/build-mcp.js",
|
|
50768
50765
|
"build:ui": "vite build --config src/ui/vite.config.ts",
|
|
50769
|
-
|
|
50770
|
-
"dev
|
|
50771
|
-
"dev:
|
|
50766
|
+
"kill:ports": "node scripts/kill-ports.js",
|
|
50767
|
+
"kill:dev-ports": "npm run kill:ports -- 4700 3710",
|
|
50768
|
+
"dev:links": "node scripts/dev-links.js",
|
|
50769
|
+
dev: `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
|
|
50770
|
+
"dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
50771
|
+
"dev:room:isolated": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
50772
|
+
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
50773
|
+
"doctor:split": "node scripts/doctor-split.js",
|
|
50774
|
+
"dev:isolated": `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & wait'`,
|
|
50775
|
+
"dev:cloud": `sh -c 'npm run kill:ports -- 3710 && cd ../cloud && PORT=3710 CLOUD_PUBLIC_URL=http://127.0.0.1:3710 CLOUD_ALLOWED_ORIGINS='"'"'http://127.0.0.1:3710,http://localhost:3710,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'"'"' npm start'`,
|
|
50772
50776
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
50773
50777
|
"seed:style-demo": "node scripts/seed-style-demo.js",
|
|
50774
50778
|
typecheck: "tsc --noEmit",
|
|
50775
50779
|
test: "npm run rebuild:native:node && vitest run --pool=forks --passWithNoTests",
|
|
50776
50780
|
"test:watch": "npm run rebuild:native:node && vitest --pool=forks",
|
|
50781
|
+
"test:quick": "npm run typecheck && npm run test",
|
|
50782
|
+
"test:smart-e2e": "git diff --cached --name-only | grep -qE '^(src/ui/|src/server/|e2e/|playwright)' && npm run test:e2e || echo 'No UI/server changes \u2014 skipping E2E'",
|
|
50777
50783
|
"test:e2e": "npm run build && npx playwright test",
|
|
50778
50784
|
"test:e2e:ui": "npm run build && npx playwright test e2e/ui.test.ts",
|
|
50785
|
+
"test:e2e:setup": "npm run build && npx playwright test e2e/setup-flow.test.ts",
|
|
50786
|
+
"test:e2e:setup:headed": "npm run build && npx playwright test e2e/setup-flow.test.ts --headed --project=chromium",
|
|
50787
|
+
"test:e2e:providers": "npm run build && npx playwright test e2e/provider-flows.test.ts",
|
|
50779
50788
|
"rebuild:native:node": `node -e "try{require('better-sqlite3')(':memory:').close()}catch{process.exit(1)}" || npx --yes node-gyp rebuild --directory=node_modules/better-sqlite3`,
|
|
50780
50789
|
prepublishOnly: "npm run build:mcp"
|
|
50781
50790
|
},
|
|
@@ -52517,7 +52526,7 @@ var init_updateChecker = __esm({
|
|
|
52517
52526
|
function getVersion3() {
|
|
52518
52527
|
if (cachedVersion) return cachedVersion;
|
|
52519
52528
|
try {
|
|
52520
|
-
cachedVersion =
|
|
52529
|
+
cachedVersion = true ? "0.1.11" : null.version;
|
|
52521
52530
|
} catch {
|
|
52522
52531
|
cachedVersion = "unknown";
|
|
52523
52532
|
}
|
|
@@ -52713,12 +52722,12 @@ function registerStatusRoutes(router) {
|
|
|
52713
52722
|
const ollama = await checkOllama();
|
|
52714
52723
|
const resources = getResources();
|
|
52715
52724
|
const deploymentMode = getDeploymentMode();
|
|
52725
|
+
const isCloud = deploymentMode === "cloud";
|
|
52716
52726
|
return {
|
|
52717
52727
|
data: {
|
|
52718
52728
|
version: getVersion3(),
|
|
52719
52729
|
uptime: Math.floor((Date.now() - startedAt) / 1e3),
|
|
52720
|
-
dataDir,
|
|
52721
|
-
dbPath,
|
|
52730
|
+
...!isCloud && { dataDir, dbPath },
|
|
52722
52731
|
claude,
|
|
52723
52732
|
codex,
|
|
52724
52733
|
ollama,
|
|
@@ -57952,6 +57961,23 @@ function serveStatic(staticDir, pathname, res) {
|
|
|
57952
57961
|
res.end("Not Found");
|
|
57953
57962
|
}
|
|
57954
57963
|
}
|
|
57964
|
+
function checkRateLimit2(ip, method) {
|
|
57965
|
+
const isWrite = method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
|
|
57966
|
+
const limit = isWrite ? RATE_LIMIT_WRITE : RATE_LIMIT_READ;
|
|
57967
|
+
const key = `${ip}:${isWrite ? "w" : "r"}`;
|
|
57968
|
+
const now = Date.now();
|
|
57969
|
+
let bucket = rateBuckets.get(key);
|
|
57970
|
+
if (!bucket || now >= bucket.resetAt) {
|
|
57971
|
+
bucket = { count: 0, resetAt: now + RATE_LIMIT_WINDOW_MS };
|
|
57972
|
+
rateBuckets.set(key, bucket);
|
|
57973
|
+
}
|
|
57974
|
+
bucket.count++;
|
|
57975
|
+
if (bucket.count > limit) {
|
|
57976
|
+
const retryAfter = Math.ceil((bucket.resetAt - now) / 1e3);
|
|
57977
|
+
return { allowed: false, retryAfter };
|
|
57978
|
+
}
|
|
57979
|
+
return { allowed: true, retryAfter: 0 };
|
|
57980
|
+
}
|
|
57955
57981
|
function createApiServer(options = {}) {
|
|
57956
57982
|
const db3 = options.db ?? getServerDatabase();
|
|
57957
57983
|
const port = options.port ?? DEFAULT_PORT;
|
|
@@ -57976,7 +58002,20 @@ function createApiServer(options = {}) {
|
|
|
57976
58002
|
const responseHeaders = {
|
|
57977
58003
|
"Content-Type": "application/json"
|
|
57978
58004
|
};
|
|
58005
|
+
if (isCloudDeployment()) {
|
|
58006
|
+
Object.assign(responseHeaders, CLOUD_SECURITY_HEADERS);
|
|
58007
|
+
}
|
|
57979
58008
|
setCorsHeaders(origin, responseHeaders);
|
|
58009
|
+
if (isCloudDeployment() && pathname.startsWith("/api/")) {
|
|
58010
|
+
const clientIp = req.socket.remoteAddress || "unknown";
|
|
58011
|
+
const rl = checkRateLimit2(clientIp, req.method || "GET");
|
|
58012
|
+
if (!rl.allowed) {
|
|
58013
|
+
responseHeaders["Retry-After"] = String(rl.retryAfter);
|
|
58014
|
+
res.writeHead(429, responseHeaders);
|
|
58015
|
+
res.end(JSON.stringify({ error: "Too many requests" }));
|
|
58016
|
+
return;
|
|
58017
|
+
}
|
|
58018
|
+
}
|
|
57980
58019
|
const isSameOrigin = origin && req.headers.host && (() => {
|
|
57981
58020
|
try {
|
|
57982
58021
|
return new import_node_url.URL(origin).host === req.headers.host;
|
|
@@ -58210,7 +58249,7 @@ function startServer(options = {}) {
|
|
|
58210
58249
|
process.exit(0);
|
|
58211
58250
|
});
|
|
58212
58251
|
}
|
|
58213
|
-
var import_node_http, import_node_https2, import_node_url, import_node_fs3, import_node_path4, import_node_os5, import_node_child_process6, DEFAULT_PORT, DEFAULT_BIND_HOST_LOCAL, DEFAULT_BIND_HOST_CLOUD, MIME_TYPES;
|
|
58252
|
+
var import_node_http, import_node_https2, import_node_url, import_node_fs3, import_node_path4, import_node_os5, import_node_child_process6, DEFAULT_PORT, DEFAULT_BIND_HOST_LOCAL, DEFAULT_BIND_HOST_CLOUD, MIME_TYPES, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_READ, RATE_LIMIT_WRITE, rateBuckets, CLOUD_SECURITY_HEADERS;
|
|
58214
58253
|
var init_server4 = __esm({
|
|
58215
58254
|
"src/server/index.ts"() {
|
|
58216
58255
|
"use strict";
|
|
@@ -58254,6 +58293,22 @@ var init_server4 = __esm({
|
|
|
58254
58293
|
".webp": "image/webp",
|
|
58255
58294
|
".webmanifest": "application/manifest+json"
|
|
58256
58295
|
};
|
|
58296
|
+
RATE_LIMIT_WINDOW_MS = 6e4;
|
|
58297
|
+
RATE_LIMIT_READ = 300;
|
|
58298
|
+
RATE_LIMIT_WRITE = 120;
|
|
58299
|
+
rateBuckets = /* @__PURE__ */ new Map();
|
|
58300
|
+
setInterval(() => {
|
|
58301
|
+
const now = Date.now();
|
|
58302
|
+
for (const [key, bucket] of rateBuckets) {
|
|
58303
|
+
if (now >= bucket.resetAt) rateBuckets.delete(key);
|
|
58304
|
+
}
|
|
58305
|
+
}, 12e4).unref();
|
|
58306
|
+
CLOUD_SECURITY_HEADERS = {
|
|
58307
|
+
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
|
|
58308
|
+
"X-Content-Type-Options": "nosniff",
|
|
58309
|
+
"X-Frame-Options": "DENY",
|
|
58310
|
+
"Referrer-Policy": "strict-origin-when-cross-origin"
|
|
58311
|
+
};
|
|
58257
58312
|
}
|
|
58258
58313
|
});
|
|
58259
58314
|
|
package/out/mcp/server.js
CHANGED
|
@@ -47101,7 +47101,7 @@ function registerResourceTools(server) {
|
|
|
47101
47101
|
async function main() {
|
|
47102
47102
|
const server = new McpServer({
|
|
47103
47103
|
name: "quoroom",
|
|
47104
|
-
version: true ? "0.1.
|
|
47104
|
+
version: true ? "0.1.11" : "0.0.0"
|
|
47105
47105
|
});
|
|
47106
47106
|
registerMemoryTools(server);
|
|
47107
47107
|
registerSchedulerTools(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quoroom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Autonomous AI agent collective engine — Queen, Workers, Quorum",
|
|
5
5
|
"main": "./out/mcp/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,16 +22,28 @@
|
|
|
22
22
|
"build": "npm run typecheck && npm run build:mcp && npm run build:ui",
|
|
23
23
|
"build:mcp": "node scripts/build-mcp.js",
|
|
24
24
|
"build:ui": "vite build --config src/ui/vite.config.ts",
|
|
25
|
-
"
|
|
26
|
-
"dev
|
|
27
|
-
"dev:
|
|
25
|
+
"kill:ports": "node scripts/kill-ports.js",
|
|
26
|
+
"kill:dev-ports": "npm run kill:ports -- 4700 3710",
|
|
27
|
+
"dev:links": "node scripts/dev-links.js",
|
|
28
|
+
"dev": "sh -c 'npm run kill:dev-ports && trap \"kill 0\" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'",
|
|
29
|
+
"dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
30
|
+
"dev:room:isolated": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
31
|
+
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
32
|
+
"doctor:split": "node scripts/doctor-split.js",
|
|
33
|
+
"dev:isolated": "sh -c 'npm run kill:dev-ports && trap \"kill 0\" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & wait'",
|
|
34
|
+
"dev:cloud": "sh -c 'npm run kill:ports -- 3710 && cd ../cloud && PORT=3710 CLOUD_PUBLIC_URL=http://127.0.0.1:3710 CLOUD_ALLOWED_ORIGINS='\"'\"'http://127.0.0.1:3710,http://localhost:3710,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'\"'\"' npm start'",
|
|
28
35
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
29
36
|
"seed:style-demo": "node scripts/seed-style-demo.js",
|
|
30
37
|
"typecheck": "tsc --noEmit",
|
|
31
38
|
"test": "npm run rebuild:native:node && vitest run --pool=forks --passWithNoTests",
|
|
32
39
|
"test:watch": "npm run rebuild:native:node && vitest --pool=forks",
|
|
40
|
+
"test:quick": "npm run typecheck && npm run test",
|
|
41
|
+
"test:smart-e2e": "git diff --cached --name-only | grep -qE '^(src/ui/|src/server/|e2e/|playwright)' && npm run test:e2e || echo 'No UI/server changes — skipping E2E'",
|
|
33
42
|
"test:e2e": "npm run build && npx playwright test",
|
|
34
43
|
"test:e2e:ui": "npm run build && npx playwright test e2e/ui.test.ts",
|
|
44
|
+
"test:e2e:setup": "npm run build && npx playwright test e2e/setup-flow.test.ts",
|
|
45
|
+
"test:e2e:setup:headed": "npm run build && npx playwright test e2e/setup-flow.test.ts --headed --project=chromium",
|
|
46
|
+
"test:e2e:providers": "npm run build && npx playwright test e2e/provider-flows.test.ts",
|
|
35
47
|
"rebuild:native:node": "node -e \"try{require('better-sqlite3')(':memory:').close()}catch{process.exit(1)}\" || npx --yes node-gyp rebuild --directory=node_modules/better-sqlite3",
|
|
36
48
|
"prepublishOnly": "npm run build:mcp"
|
|
37
49
|
},
|