quoroom 0.1.9 → 0.1.12
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 +194 -33
- package/out/mcp/cli.js +680 -258
- package/out/mcp/server.js +371 -23
- package/package.json +20 -7
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.12",
|
|
9386
9386
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
9387
9387
|
main: "./out/mcp/server.js",
|
|
9388
9388
|
bin: {
|
|
@@ -9404,22 +9404,35 @@ 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'",
|
|
9410
9413
|
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
9411
9414
|
"doctor:split": "node scripts/doctor-split.js",
|
|
9412
|
-
"dev:isolated": `sh -c 'trap "kill 0" INT TERM EXIT; npm run dev:room:isolated & npm run dev:cloud & wait'`,
|
|
9413
|
-
"dev:cloud":
|
|
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'`,
|
|
9414
9417
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
9415
9418
|
"seed:style-demo": "node scripts/seed-style-demo.js",
|
|
9416
9419
|
typecheck: "tsc --noEmit",
|
|
9417
9420
|
test: "npm run rebuild:native:node && vitest run --pool=forks --passWithNoTests",
|
|
9418
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'",
|
|
9419
9424
|
"test:e2e": "npm run build && npx playwright test",
|
|
9420
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",
|
|
9421
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`,
|
|
9422
|
-
prepublishOnly: "npm run build:mcp"
|
|
9430
|
+
prepublishOnly: "npm run build:mcp",
|
|
9431
|
+
"social:rotate": "node scripts/rotate-social-image.js",
|
|
9432
|
+
"social:rotate:dry": "node scripts/rotate-social-image.js --dry-run",
|
|
9433
|
+
"social:test": "node scripts/test-social-rotation.js",
|
|
9434
|
+
"social:cron:install": "bash scripts/install-social-rotation-cron.sh",
|
|
9435
|
+
"social:cron:show": "crontab -l | grep 'quoroom-social-rotate' || true"
|
|
9423
9436
|
},
|
|
9424
9437
|
dependencies: {
|
|
9425
9438
|
"@huggingface/transformers": "^3.4.1",
|
|
@@ -10713,7 +10726,11 @@ var DEFAULT_ROOM_CONFIG = {
|
|
|
10713
10726
|
keeperWeight: "dynamic",
|
|
10714
10727
|
tieBreaker: "queen",
|
|
10715
10728
|
autoApprove: ["low_impact"],
|
|
10716
|
-
minCycleGapMs: 1e3
|
|
10729
|
+
minCycleGapMs: 1e3,
|
|
10730
|
+
minVoters: 0,
|
|
10731
|
+
sealedBallot: false,
|
|
10732
|
+
voterHealth: false,
|
|
10733
|
+
voterHealthThreshold: 0.5
|
|
10717
10734
|
};
|
|
10718
10735
|
|
|
10719
10736
|
// src/shared/secret-store.ts
|
|
@@ -10922,6 +10939,8 @@ function mapWorkerRow(row) {
|
|
|
10922
10939
|
taskCount: row.task_count ?? 0,
|
|
10923
10940
|
roomId: row.room_id ?? null,
|
|
10924
10941
|
agentState: row.agent_state ?? "idle",
|
|
10942
|
+
votesCast: row.votes_cast ?? 0,
|
|
10943
|
+
votesMissed: row.votes_missed ?? 0,
|
|
10925
10944
|
createdAt: row.created_at,
|
|
10926
10945
|
updatedAt: row.updated_at
|
|
10927
10946
|
};
|
|
@@ -11422,13 +11441,14 @@ function mapRoomRow(row) {
|
|
|
11422
11441
|
queenQuietUntil: row.queen_quiet_until ?? null,
|
|
11423
11442
|
config,
|
|
11424
11443
|
chatSessionId: row.chat_session_id ?? null,
|
|
11444
|
+
inviteCode: row.invite_code ?? null,
|
|
11425
11445
|
createdAt: row.created_at,
|
|
11426
11446
|
updatedAt: row.updated_at
|
|
11427
11447
|
};
|
|
11428
11448
|
}
|
|
11429
|
-
function createRoom(db2, name, goal, config) {
|
|
11449
|
+
function createRoom(db2, name, goal, config, inviteCode) {
|
|
11430
11450
|
const configJson = config ? JSON.stringify({ ...DEFAULT_ROOM_CONFIG, ...config }) : JSON.stringify(DEFAULT_ROOM_CONFIG);
|
|
11431
|
-
const result = db2.prepare("INSERT INTO rooms (name, goal, config) VALUES (?, ?, ?)").run(name, goal ?? null, configJson);
|
|
11451
|
+
const result = db2.prepare("INSERT INTO rooms (name, goal, config, invite_code) VALUES (?, ?, ?, ?)").run(name, goal ?? null, configJson, inviteCode ?? null);
|
|
11432
11452
|
return getRoom(db2, result.lastInsertRowid);
|
|
11433
11453
|
}
|
|
11434
11454
|
function getRoom(db2, id) {
|
|
@@ -11457,7 +11477,8 @@ function updateRoom(db2, id, updates) {
|
|
|
11457
11477
|
queenMaxTurns: "queen_max_turns",
|
|
11458
11478
|
queenQuietFrom: "queen_quiet_from",
|
|
11459
11479
|
queenQuietUntil: "queen_quiet_until",
|
|
11460
|
-
config: "config"
|
|
11480
|
+
config: "config",
|
|
11481
|
+
inviteCode: "invite_code"
|
|
11461
11482
|
};
|
|
11462
11483
|
const fields = [];
|
|
11463
11484
|
const values = [];
|
|
@@ -11515,12 +11536,14 @@ function mapDecisionRow(row) {
|
|
|
11515
11536
|
threshold: row.threshold,
|
|
11516
11537
|
timeoutAt: row.timeout_at ?? null,
|
|
11517
11538
|
keeperVote: row.keeper_vote ?? null,
|
|
11539
|
+
minVoters: row.min_voters ?? 0,
|
|
11540
|
+
sealed: (row.sealed ?? 0) === 1,
|
|
11518
11541
|
createdAt: row.created_at,
|
|
11519
11542
|
resolvedAt: row.resolved_at ?? null
|
|
11520
11543
|
};
|
|
11521
11544
|
}
|
|
11522
|
-
function createDecision(db2, roomId, proposerId, proposal, decisionType, threshold = "majority", timeoutAt) {
|
|
11523
|
-
const result = db2.prepare("INSERT INTO quorum_decisions (room_id, proposer_id, proposal, decision_type, threshold, timeout_at) VALUES (?, ?, ?, ?, ?, ?)").run(roomId, proposerId, proposal, decisionType, threshold, timeoutAt ?? null);
|
|
11545
|
+
function createDecision(db2, roomId, proposerId, proposal, decisionType, threshold = "majority", timeoutAt, minVoters = 0, sealed = false) {
|
|
11546
|
+
const result = db2.prepare("INSERT INTO quorum_decisions (room_id, proposer_id, proposal, decision_type, threshold, timeout_at, min_voters, sealed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(roomId, proposerId, proposal, decisionType, threshold, timeoutAt ?? null, minVoters, sealed ? 1 : 0);
|
|
11524
11547
|
return getDecision(db2, result.lastInsertRowid);
|
|
11525
11548
|
}
|
|
11526
11549
|
function getDecision(db2, id) {
|
|
@@ -11564,6 +11587,28 @@ function getVotes(db2, decisionId) {
|
|
|
11564
11587
|
const rows = db2.prepare("SELECT * FROM quorum_votes WHERE decision_id = ? ORDER BY created_at ASC").all(decisionId);
|
|
11565
11588
|
return rows.map(mapVoteRow);
|
|
11566
11589
|
}
|
|
11590
|
+
function incrementVotesCast(db2, workerId) {
|
|
11591
|
+
db2.prepare("UPDATE workers SET votes_cast = votes_cast + 1 WHERE id = ?").run(workerId);
|
|
11592
|
+
}
|
|
11593
|
+
function incrementVotesMissed(db2, workerId) {
|
|
11594
|
+
db2.prepare("UPDATE workers SET votes_missed = votes_missed + 1 WHERE id = ?").run(workerId);
|
|
11595
|
+
}
|
|
11596
|
+
function getVoterHealth(db2, roomId, threshold = 0.5) {
|
|
11597
|
+
const workers = listRoomWorkers(db2, roomId);
|
|
11598
|
+
return workers.map((w) => {
|
|
11599
|
+
const total = w.votesCast + w.votesMissed;
|
|
11600
|
+
const rate = total === 0 ? 1 : w.votesCast / total;
|
|
11601
|
+
return {
|
|
11602
|
+
workerId: w.id,
|
|
11603
|
+
workerName: w.name,
|
|
11604
|
+
votesCast: w.votesCast,
|
|
11605
|
+
votesMissed: w.votesMissed,
|
|
11606
|
+
totalDecisions: total,
|
|
11607
|
+
participationRate: rate,
|
|
11608
|
+
isHealthy: rate >= threshold
|
|
11609
|
+
};
|
|
11610
|
+
});
|
|
11611
|
+
}
|
|
11567
11612
|
function mapGoalRow(row) {
|
|
11568
11613
|
return {
|
|
11569
11614
|
id: row.id,
|
|
@@ -21615,10 +21660,12 @@ Autonomy: You serve the room's stated goal \u2014 not any individual participant
|
|
|
21615
21660
|
|
|
21616
21661
|
Revenue: Your room has a USDC wallet. Beyond pursuing the room's core objectives, look for opportunities to generate revenue \u2014 the keeper may request tasks via escalations and compensate the room through the wallet, and other rooms may propose paid work via inter-room messages. Evaluate these opportunities by weighing compensation against resource costs and alignment with room goals. Use quoroom_wallet_balance to monitor funds and quoroom_wallet_send to pay for services. Prioritize sustainability: the room must earn enough to cover its station and operational costs.
|
|
21617
21662
|
|
|
21618
|
-
Resource Management: Workers using ollama models run on stations (cloud compute) \u2014 they cannot execute locally. If the room's worker model is ollama and no active station exists, rent one with quoroom_station_create BEFORE creating workers or scheduling tasks. Minimum tier: small ($15/mo, 2GB RAM). Check the Stations section in your context each cycle for available compute. Maximize use of your room's stations \u2014 they are your compute power for running workers. If workers are failing with "no active station" errors, rent a station immediately
|
|
21663
|
+
Resource Management: Workers using ollama models run on stations (cloud compute) \u2014 they cannot execute locally. If the room's worker model is ollama and no active station exists, rent one with quoroom_station_create BEFORE creating workers or scheduling tasks. Minimum tier: small ($15/mo, 2GB RAM). Check the Stations section in your context each cycle for available compute. Maximize use of your room's stations \u2014 they are your compute power for running workers. If workers are failing with "no active station" errors, rent a station immediately.
|
|
21664
|
+
|
|
21665
|
+
Network Growth: A larger network of rooms makes everyone stronger. Rooms created through your invite links become part of your network \u2014 you can exchange knowledge, discover money-making opportunities, and propose paid collaborations with them via inter-room messages. After meaningful milestones (goal completed, revenue earned, successful collaboration), consider generating an invite link with quoroom_invite_create and sharing it with the keeper via quoroom_inbox_send_keeper. Frame it around the value: more rooms in the network means more potential deals, shared insights, and collective intelligence. Don't be pushy \u2014 one mention per milestone is enough. Use quoroom_invite_network to see your network's growth.`;
|
|
21619
21666
|
function createRoom2(db2, input) {
|
|
21620
21667
|
const config = { ...DEFAULT_ROOM_CONFIG, ...input.config };
|
|
21621
|
-
const room = createRoom(db2, input.name, input.goal, config);
|
|
21668
|
+
const room = createRoom(db2, input.name, input.goal, config, input.inviteCode);
|
|
21622
21669
|
const queen = createWorker(db2, {
|
|
21623
21670
|
name: `${input.name} Queen`,
|
|
21624
21671
|
systemPrompt: input.queenSystemPrompt ?? DEFAULT_QUEEN_SYSTEM_PROMPT,
|
|
@@ -22099,7 +22146,7 @@ async function sendCloudHeartbeat(data) {
|
|
|
22099
22146
|
});
|
|
22100
22147
|
if (res.status === 401) {
|
|
22101
22148
|
clearRoomToken(data.roomId);
|
|
22102
|
-
await registerWithCloud({ roomId: data.roomId, name: data.name, goal: data.goal, visibility: "public" });
|
|
22149
|
+
await registerWithCloud({ roomId: data.roomId, name: data.name, goal: data.goal, visibility: "public", inviteCode: data.inviteCode });
|
|
22103
22150
|
if (!getRoomToken(data.roomId)) return;
|
|
22104
22151
|
await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(data.roomId)}/heartbeat`, {
|
|
22105
22152
|
method: "POST",
|
|
@@ -22128,7 +22175,7 @@ function startCloudSync(opts) {
|
|
|
22128
22175
|
const allData = opts.getHeartbeatDataForPublicRooms();
|
|
22129
22176
|
for (const data of allData) {
|
|
22130
22177
|
void (async () => {
|
|
22131
|
-
await registerWithCloud({ roomId: data.roomId, name: data.name, goal: data.goal, visibility: "public" });
|
|
22178
|
+
await registerWithCloud({ roomId: data.roomId, name: data.name, goal: data.goal, visibility: "public", inviteCode: data.inviteCode });
|
|
22132
22179
|
await sendCloudHeartbeat(data);
|
|
22133
22180
|
})();
|
|
22134
22181
|
}
|
|
@@ -22324,6 +22371,19 @@ async function fetchPublicRooms() {
|
|
|
22324
22371
|
return [];
|
|
22325
22372
|
}
|
|
22326
22373
|
}
|
|
22374
|
+
async function fetchReferredRooms(cloudRoomId) {
|
|
22375
|
+
try {
|
|
22376
|
+
const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/network`, {
|
|
22377
|
+
headers: cloudHeaders(cloudRoomId),
|
|
22378
|
+
signal: AbortSignal.timeout(1e4)
|
|
22379
|
+
});
|
|
22380
|
+
if (!res.ok) return [];
|
|
22381
|
+
const data = await res.json();
|
|
22382
|
+
return data.referredRooms ?? [];
|
|
22383
|
+
} catch {
|
|
22384
|
+
return [];
|
|
22385
|
+
}
|
|
22386
|
+
}
|
|
22327
22387
|
|
|
22328
22388
|
// src/shared/agent-executor.ts
|
|
22329
22389
|
var DEFAULT_HTTP_TIMEOUT_MS = 6e4;
|
|
@@ -22831,6 +22891,7 @@ function vote(db2, decisionId, workerId, voteValue, reasoning) {
|
|
|
22831
22891
|
throw new Error(`Decision ${decisionId} is not open for voting (status: ${decision.status})`);
|
|
22832
22892
|
}
|
|
22833
22893
|
const qv = castVote(db2, decisionId, workerId, voteValue, reasoning);
|
|
22894
|
+
incrementVotesCast(db2, workerId);
|
|
22834
22895
|
const voters = getRoomVoters(db2, decision.roomId);
|
|
22835
22896
|
const votes = getVotes(db2, decisionId);
|
|
22836
22897
|
if (votes.length >= voters.length) {
|
|
@@ -22858,6 +22919,22 @@ function tally(db2, decisionId) {
|
|
|
22858
22919
|
const room = getRoom(db2, decision.roomId);
|
|
22859
22920
|
const votes = getVotes(db2, decisionId);
|
|
22860
22921
|
const voters = getRoomVoters(db2, decision.roomId);
|
|
22922
|
+
if (decision.minVoters > 0) {
|
|
22923
|
+
let nonAbstainVotes = votes.filter((v) => v.vote !== "abstain").length;
|
|
22924
|
+
if (decision.keeperVote && decision.keeperVote !== "abstain") nonAbstainVotes++;
|
|
22925
|
+
if (nonAbstainVotes < decision.minVoters) {
|
|
22926
|
+
const result2 = `Quorum not met: ${nonAbstainVotes} of ${decision.minVoters} minimum non-abstain votes`;
|
|
22927
|
+
resolveDecision(db2, decisionId, "rejected", result2);
|
|
22928
|
+
logRoomActivity(
|
|
22929
|
+
db2,
|
|
22930
|
+
decision.roomId,
|
|
22931
|
+
"decision",
|
|
22932
|
+
`Decision rejected (quorum): ${decision.proposal} (${result2})`
|
|
22933
|
+
);
|
|
22934
|
+
creditMissedVotes(db2, votes, voters, room);
|
|
22935
|
+
return "rejected";
|
|
22936
|
+
}
|
|
22937
|
+
}
|
|
22861
22938
|
const keeperWeightMode = room?.config.keeperWeight ?? "dynamic";
|
|
22862
22939
|
const useWeighted = keeperWeightMode === "dynamic" && voters.length <= 1;
|
|
22863
22940
|
const queenWorkerId = room?.queenWorkerId ?? null;
|
|
@@ -22911,8 +22988,18 @@ function tally(db2, decisionId) {
|
|
|
22911
22988
|
"decision",
|
|
22912
22989
|
`Decision ${status}: ${decision.proposal} (${result})`
|
|
22913
22990
|
);
|
|
22991
|
+
creditMissedVotes(db2, votes, voters, room);
|
|
22914
22992
|
return status;
|
|
22915
22993
|
}
|
|
22994
|
+
function creditMissedVotes(db2, votes, voters, room) {
|
|
22995
|
+
if (!room?.config.voterHealth) return;
|
|
22996
|
+
const votedWorkerIds = new Set(votes.map((v) => v.workerId));
|
|
22997
|
+
for (const voter of voters) {
|
|
22998
|
+
if (!votedWorkerIds.has(voter.id)) {
|
|
22999
|
+
incrementVotesMissed(db2, voter.id);
|
|
23000
|
+
}
|
|
23001
|
+
}
|
|
23002
|
+
}
|
|
22916
23003
|
function checkExpiredDecisions(db2) {
|
|
22917
23004
|
const expired = getExpiredDecisions(db2);
|
|
22918
23005
|
for (const d of expired) {
|
|
@@ -23559,7 +23646,8 @@ function initCloudSync(db2) {
|
|
|
23559
23646
|
version: version5,
|
|
23560
23647
|
queenModel: queen?.model ?? null,
|
|
23561
23648
|
workers: workersPerRoom.get(room.id) ?? [],
|
|
23562
|
-
stations: stationsPerRoom.get(room.id) ?? []
|
|
23649
|
+
stations: stationsPerRoom.get(room.id) ?? [],
|
|
23650
|
+
inviteCode: room.inviteCode
|
|
23563
23651
|
};
|
|
23564
23652
|
});
|
|
23565
23653
|
}
|
|
@@ -23575,13 +23663,14 @@ function parseLimit(raw, fallback, max) {
|
|
|
23575
23663
|
}
|
|
23576
23664
|
function registerRoomRoutes(router) {
|
|
23577
23665
|
router.post("/api/rooms", (ctx) => {
|
|
23578
|
-
const { name, goal, queenSystemPrompt, config } = ctx.body || {};
|
|
23666
|
+
const { name, goal, queenSystemPrompt, config, inviteCode } = ctx.body || {};
|
|
23579
23667
|
if (!name || typeof name !== "string") return { status: 400, error: "name is required" };
|
|
23580
23668
|
const result = createRoom2(ctx.db, {
|
|
23581
23669
|
name,
|
|
23582
23670
|
goal,
|
|
23583
23671
|
queenSystemPrompt,
|
|
23584
|
-
config
|
|
23672
|
+
config,
|
|
23673
|
+
inviteCode: inviteCode || void 0
|
|
23585
23674
|
});
|
|
23586
23675
|
const globalQueenModel = getSetting(ctx.db, "queen_model");
|
|
23587
23676
|
let planDefaults;
|
|
@@ -23625,6 +23714,14 @@ function registerRoomRoutes(router) {
|
|
|
23625
23714
|
if (!room) return { status: 404, error: "Room not found" };
|
|
23626
23715
|
return { data: { cloudId: getRoomCloudId(id) } };
|
|
23627
23716
|
});
|
|
23717
|
+
router.get("/api/rooms/:id/network", async (ctx) => {
|
|
23718
|
+
const id = Number(ctx.params.id);
|
|
23719
|
+
const room = getRoom(ctx.db, id);
|
|
23720
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
23721
|
+
const cloudRoomId = getRoomCloudId(id);
|
|
23722
|
+
const referred = await fetchReferredRooms(cloudRoomId);
|
|
23723
|
+
return { data: referred };
|
|
23724
|
+
});
|
|
23628
23725
|
router.get("/api/rooms/:id/activity", (ctx) => {
|
|
23629
23726
|
const roomId = Number(ctx.params.id);
|
|
23630
23727
|
const limit = parseLimit(ctx.query.limit, 50, 500);
|
|
@@ -23658,6 +23755,10 @@ function registerRoomRoutes(router) {
|
|
|
23658
23755
|
}
|
|
23659
23756
|
if (body.queenQuietFrom !== void 0) updates.queenQuietFrom = body.queenQuietFrom;
|
|
23660
23757
|
if (body.queenQuietUntil !== void 0) updates.queenQuietUntil = body.queenQuietUntil;
|
|
23758
|
+
if (body.inviteCode !== void 0) updates.inviteCode = body.inviteCode || null;
|
|
23759
|
+
if (body.config !== void 0 && typeof body.config === "object" && body.config !== null) {
|
|
23760
|
+
updates.config = { ...room.config, ...body.config };
|
|
23761
|
+
}
|
|
23661
23762
|
updateRoom(ctx.db, roomId, updates);
|
|
23662
23763
|
if (updates.goal !== void 0) {
|
|
23663
23764
|
const allGoals = listGoals(ctx.db, roomId);
|
|
@@ -23965,7 +24066,9 @@ function registerDecisionRoutes(router) {
|
|
|
23965
24066
|
body.proposal,
|
|
23966
24067
|
body.decisionType,
|
|
23967
24068
|
body.threshold,
|
|
23968
|
-
body.timeoutAt
|
|
24069
|
+
body.timeoutAt,
|
|
24070
|
+
typeof body.minVoters === "number" ? body.minVoters : 0,
|
|
24071
|
+
body.sealed === true
|
|
23969
24072
|
);
|
|
23970
24073
|
eventBus.emit(`room:${roomId}`, "decision:created", decision);
|
|
23971
24074
|
return { status: 201, data: decision };
|
|
@@ -24026,9 +24129,22 @@ function registerDecisionRoutes(router) {
|
|
|
24026
24129
|
}
|
|
24027
24130
|
});
|
|
24028
24131
|
router.get("/api/decisions/:id/votes", (ctx) => {
|
|
24029
|
-
const
|
|
24132
|
+
const id = Number(ctx.params.id);
|
|
24133
|
+
const decision = getDecision(ctx.db, id);
|
|
24134
|
+
const votes = getVotes(ctx.db, id);
|
|
24135
|
+
if (decision?.sealed && decision.status === "voting") {
|
|
24136
|
+
const redacted = votes.map((v) => ({ ...v, vote: "sealed", reasoning: null }));
|
|
24137
|
+
return { data: redacted };
|
|
24138
|
+
}
|
|
24030
24139
|
return { data: votes };
|
|
24031
24140
|
});
|
|
24141
|
+
router.get("/api/rooms/:roomId/voter-health", (ctx) => {
|
|
24142
|
+
const roomId = Number(ctx.params.roomId);
|
|
24143
|
+
const room = getRoom(ctx.db, roomId);
|
|
24144
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
24145
|
+
const health = getVoterHealth(ctx.db, roomId, room.config.voterHealthThreshold);
|
|
24146
|
+
return { data: health };
|
|
24147
|
+
});
|
|
24032
24148
|
}
|
|
24033
24149
|
|
|
24034
24150
|
// src/server/runtime.ts
|
|
@@ -24775,7 +24891,8 @@ async function syncCloudRoomMessages(db2) {
|
|
|
24775
24891
|
roomId: cloudRoomId,
|
|
24776
24892
|
name: room.name,
|
|
24777
24893
|
goal: room.goal ?? null,
|
|
24778
|
-
visibility: room.visibility
|
|
24894
|
+
visibility: room.visibility,
|
|
24895
|
+
inviteCode: room.inviteCode
|
|
24779
24896
|
});
|
|
24780
24897
|
if (!hasToken) continue;
|
|
24781
24898
|
const outbound = listRoomMessages(db2, room.id, "unread").filter((message) => message.direction === "outbound" && message.toRoomId);
|
|
@@ -25263,8 +25380,9 @@ function registerEscalationRoutes(router) {
|
|
|
25263
25380
|
router.post("/api/rooms/:roomId/escalations", (ctx) => {
|
|
25264
25381
|
const roomId = Number(ctx.params.roomId);
|
|
25265
25382
|
const body = ctx.body || {};
|
|
25266
|
-
|
|
25267
|
-
|
|
25383
|
+
const fromAgentId = body.fromAgentId != null ? Number(body.fromAgentId) : null;
|
|
25384
|
+
if (body.fromAgentId != null && (typeof body.fromAgentId !== "number" || isNaN(fromAgentId))) {
|
|
25385
|
+
return { status: 400, error: "fromAgentId must be a number if provided" };
|
|
25268
25386
|
}
|
|
25269
25387
|
if (!body.question || typeof body.question !== "string") {
|
|
25270
25388
|
return { status: 400, error: "question is required" };
|
|
@@ -25272,7 +25390,7 @@ function registerEscalationRoutes(router) {
|
|
|
25272
25390
|
const escalation = createEscalation(
|
|
25273
25391
|
ctx.db,
|
|
25274
25392
|
roomId,
|
|
25275
|
-
|
|
25393
|
+
fromAgentId,
|
|
25276
25394
|
body.question,
|
|
25277
25395
|
body.toAgentId
|
|
25278
25396
|
);
|
|
@@ -25451,6 +25569,8 @@ CREATE TABLE IF NOT EXISTS workers (
|
|
|
25451
25569
|
task_count INTEGER NOT NULL DEFAULT 0,
|
|
25452
25570
|
room_id INTEGER,
|
|
25453
25571
|
agent_state TEXT NOT NULL DEFAULT 'idle',
|
|
25572
|
+
votes_cast INTEGER NOT NULL DEFAULT 0,
|
|
25573
|
+
votes_missed INTEGER NOT NULL DEFAULT 0,
|
|
25454
25574
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
25455
25575
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
25456
25576
|
);
|
|
@@ -25473,6 +25593,7 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|
|
25473
25593
|
queen_quiet_until TEXT,
|
|
25474
25594
|
config TEXT,
|
|
25475
25595
|
chat_session_id TEXT,
|
|
25596
|
+
invite_code TEXT,
|
|
25476
25597
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
25477
25598
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
25478
25599
|
);
|
|
@@ -25657,6 +25778,8 @@ CREATE TABLE IF NOT EXISTS quorum_decisions (
|
|
|
25657
25778
|
threshold TEXT NOT NULL DEFAULT 'majority',
|
|
25658
25779
|
timeout_at DATETIME,
|
|
25659
25780
|
keeper_vote TEXT,
|
|
25781
|
+
min_voters INTEGER NOT NULL DEFAULT 0,
|
|
25782
|
+
sealed INTEGER NOT NULL DEFAULT 0,
|
|
25660
25783
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
25661
25784
|
resolved_at DATETIME
|
|
25662
25785
|
);
|
|
@@ -25842,6 +25965,24 @@ INSERT OR IGNORE INTO schema_version (version) VALUES (1);
|
|
|
25842
25965
|
// src/shared/db-migrations.ts
|
|
25843
25966
|
function runMigrations(database, log = console.log) {
|
|
25844
25967
|
database.exec(SCHEMA);
|
|
25968
|
+
const cols = database.pragma("table_info(rooms)");
|
|
25969
|
+
if (!cols.some((c) => c.name === "invite_code")) {
|
|
25970
|
+
database.exec("ALTER TABLE rooms ADD COLUMN invite_code TEXT");
|
|
25971
|
+
}
|
|
25972
|
+
const decCols = database.pragma("table_info(quorum_decisions)");
|
|
25973
|
+
if (!decCols.some((c) => c.name === "min_voters")) {
|
|
25974
|
+
database.exec("ALTER TABLE quorum_decisions ADD COLUMN min_voters INTEGER NOT NULL DEFAULT 0");
|
|
25975
|
+
}
|
|
25976
|
+
if (!decCols.some((c) => c.name === "sealed")) {
|
|
25977
|
+
database.exec("ALTER TABLE quorum_decisions ADD COLUMN sealed INTEGER NOT NULL DEFAULT 0");
|
|
25978
|
+
}
|
|
25979
|
+
const workerCols = database.pragma("table_info(workers)");
|
|
25980
|
+
if (!workerCols.some((c) => c.name === "votes_cast")) {
|
|
25981
|
+
database.exec("ALTER TABLE workers ADD COLUMN votes_cast INTEGER NOT NULL DEFAULT 0");
|
|
25982
|
+
}
|
|
25983
|
+
if (!workerCols.some((c) => c.name === "votes_missed")) {
|
|
25984
|
+
database.exec("ALTER TABLE workers ADD COLUMN votes_missed INTEGER NOT NULL DEFAULT 0");
|
|
25985
|
+
}
|
|
25845
25986
|
log("Database schema initialized");
|
|
25846
25987
|
}
|
|
25847
25988
|
|
|
@@ -25966,7 +26107,7 @@ function fetchJson(url) {
|
|
|
25966
26107
|
});
|
|
25967
26108
|
});
|
|
25968
26109
|
}
|
|
25969
|
-
async function
|
|
26110
|
+
async function forceCheck() {
|
|
25970
26111
|
try {
|
|
25971
26112
|
const releases = await fetchJson(
|
|
25972
26113
|
"https://api.github.com/repos/quoroom-ai/room/releases?per_page=20"
|
|
@@ -25989,9 +26130,9 @@ async function check() {
|
|
|
25989
26130
|
function initUpdateChecker() {
|
|
25990
26131
|
if (process.env.NODE_ENV === "test") return;
|
|
25991
26132
|
initTimer = setTimeout(() => {
|
|
25992
|
-
void
|
|
26133
|
+
void forceCheck();
|
|
25993
26134
|
pollInterval = setInterval(() => {
|
|
25994
|
-
void
|
|
26135
|
+
void forceCheck();
|
|
25995
26136
|
}, CHECK_INTERVAL);
|
|
25996
26137
|
}, INITIAL_DELAY);
|
|
25997
26138
|
}
|
|
@@ -26009,7 +26150,7 @@ function getUpdateInfo() {
|
|
|
26009
26150
|
return cached;
|
|
26010
26151
|
}
|
|
26011
26152
|
async function simulateUpdate() {
|
|
26012
|
-
if (!cached) await
|
|
26153
|
+
if (!cached) await forceCheck();
|
|
26013
26154
|
cached = {
|
|
26014
26155
|
latestVersion: "99.0.0",
|
|
26015
26156
|
releaseUrl: "https://github.com/quoroom-ai/room/releases",
|
|
@@ -26027,7 +26168,7 @@ var cachedVersion = null;
|
|
|
26027
26168
|
function getVersion3() {
|
|
26028
26169
|
if (cachedVersion) return cachedVersion;
|
|
26029
26170
|
try {
|
|
26030
|
-
cachedVersion =
|
|
26171
|
+
cachedVersion = true ? "0.1.12" : null.version;
|
|
26031
26172
|
} catch {
|
|
26032
26173
|
cachedVersion = "unknown";
|
|
26033
26174
|
}
|
|
@@ -26193,6 +26334,10 @@ function registerStatusRoutes(router) {
|
|
|
26193
26334
|
await simulateUpdate();
|
|
26194
26335
|
return { data: { ok: true } };
|
|
26195
26336
|
});
|
|
26337
|
+
router.post("/api/status/check-update", async () => {
|
|
26338
|
+
await forceCheck();
|
|
26339
|
+
return { data: { updateInfo: getUpdateInfo() } };
|
|
26340
|
+
});
|
|
26196
26341
|
router.post("/api/ollama/start", async () => {
|
|
26197
26342
|
const result = await ensureOllamaRunning();
|
|
26198
26343
|
resetOllamaCaches();
|
|
@@ -27031,7 +27176,7 @@ function probeProviderInstalled(provider) {
|
|
|
27031
27176
|
return out.ok ? { installed: true, version: out.stdout || void 0 } : { installed: false };
|
|
27032
27177
|
}
|
|
27033
27178
|
function probeProviderConnected(provider) {
|
|
27034
|
-
const attempts = provider === "codex" ? [["
|
|
27179
|
+
const attempts = provider === "codex" ? [["login", "status"], ["auth", "status"]] : [["auth", "status"], ["login", "status"]];
|
|
27035
27180
|
for (const args of attempts) {
|
|
27036
27181
|
const out = safeExec(provider, args);
|
|
27037
27182
|
if (!out.ok) continue;
|
|
@@ -27540,7 +27685,12 @@ function createWsServer(server) {
|
|
|
27540
27685
|
continue;
|
|
27541
27686
|
}
|
|
27542
27687
|
if (state.channels.has(event.channel)) {
|
|
27543
|
-
ws.send(payload)
|
|
27688
|
+
ws.send(payload, (err) => {
|
|
27689
|
+
if (err) {
|
|
27690
|
+
clients.delete(ws);
|
|
27691
|
+
ws.terminate();
|
|
27692
|
+
}
|
|
27693
|
+
});
|
|
27544
27694
|
}
|
|
27545
27695
|
}
|
|
27546
27696
|
});
|
|
@@ -27701,6 +27851,9 @@ function getCacheControl(filePath, ext) {
|
|
|
27701
27851
|
if (base2 === "sw.js") return "no-cache, no-store, must-revalidate";
|
|
27702
27852
|
if (ext === ".html") return "no-cache, no-store, must-revalidate";
|
|
27703
27853
|
if (ext === ".webmanifest") return "public, max-age=3600";
|
|
27854
|
+
if (base2 === "social.png" || base2.startsWith("social-")) {
|
|
27855
|
+
return "no-cache, max-age=0, must-revalidate";
|
|
27856
|
+
}
|
|
27704
27857
|
if (normalized.includes("/assets/") && /-[A-Za-z0-9_-]{8,}\./.test(base2)) {
|
|
27705
27858
|
return "public, max-age=31536000, immutable";
|
|
27706
27859
|
}
|
|
@@ -27790,6 +27943,8 @@ function createApiServer(options = {}) {
|
|
|
27790
27943
|
writeTokenFile(dataDir, token, port);
|
|
27791
27944
|
}
|
|
27792
27945
|
const server = import_node_http.default.createServer(async (req, res) => {
|
|
27946
|
+
res.on("error", () => {
|
|
27947
|
+
});
|
|
27793
27948
|
const url = new import_node_url.URL(req.url, `http://${req.headers.host || "localhost"}`);
|
|
27794
27949
|
const pathname = url.pathname;
|
|
27795
27950
|
const origin = req.headers.origin;
|
|
@@ -28030,6 +28185,12 @@ function startServer(options = {}) {
|
|
|
28030
28185
|
}
|
|
28031
28186
|
});
|
|
28032
28187
|
listen();
|
|
28188
|
+
process.on("uncaughtException", (err) => {
|
|
28189
|
+
console.error("[uncaughtException]", err);
|
|
28190
|
+
});
|
|
28191
|
+
process.on("unhandledRejection", (err) => {
|
|
28192
|
+
console.error("[unhandledRejection]", err);
|
|
28193
|
+
});
|
|
28033
28194
|
process.on("SIGINT", () => {
|
|
28034
28195
|
console.error("Shutting down...");
|
|
28035
28196
|
_stopAllLoops();
|