quoroom 0.1.27 → 0.1.28
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 -13
- package/out/mcp/api-server.js +176 -22
- package/out/mcp/cli.js +180 -31
- package/out/mcp/server.js +45 -20
- package/package.json +1 -1
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)
|
|
@@ -81,7 +81,7 @@ Quoroom is an open research project exploring autonomous agent collectives. Each
|
|
|
81
81
|
|
|
82
82
|
**Activity Controls** — Throttle the queen per room: configurable cycle gap (sleep between runs), max turns per cycle, and quiet hours (time window where the queen rests). Plan-aware defaults (Pro/Max/API/None) apply automatically when you create a new room based on your Claude subscription tier.
|
|
83
83
|
|
|
84
|
-
**Quorum Voting** — Agents propose and vote on decisions. Majority, supermajority, or unanimous — you choose the threshold.
|
|
84
|
+
**Quorum Voting** — Agents propose and vote on decisions. Majority, supermajority, or unanimous — you choose the threshold. All voters (keeper and workers) have equal weight. Ties are broken by the queen's vote by default.
|
|
85
85
|
|
|
86
86
|
**Goals** — Hierarchical goal decomposition with progress tracking. Set a top-level objective and let agents break it down.
|
|
87
87
|
|
|
@@ -197,6 +197,29 @@ Installer launchers:
|
|
|
197
197
|
| Linux x64 | `.deb` | `.tar.gz` |
|
|
198
198
|
| Windows x64 (signed) | `.exe` setup | `.zip` |
|
|
199
199
|
|
|
200
|
+
<details>
|
|
201
|
+
<summary>Platform notes</summary>
|
|
202
|
+
|
|
203
|
+
**macOS**
|
|
204
|
+
- Universal binary (Apple Silicon + Intel) — single `.pkg` works on both architectures
|
|
205
|
+
- Native menu bar tray app (Swift) — auto-starts server, provides "Open Dashboard" / "Restart" / "Quit"
|
|
206
|
+
- `.pkg` is codesigned + Apple-notarized
|
|
207
|
+
- Shell wrapper with auto-update check (polls GitHub every 4h) and crash rollback (3-strike reset)
|
|
208
|
+
- PATH: inherits login shell PATH at startup (`zsh -lic`) so Homebrew/NVM-installed CLIs (`claude`, `codex`) are found
|
|
209
|
+
|
|
210
|
+
**Windows**
|
|
211
|
+
- Signed `.exe` installer (NSIS, SSL.com eSigner)
|
|
212
|
+
- VBS launcher — starts server without a console window
|
|
213
|
+
- Adds `quoroom` to system PATH via registry
|
|
214
|
+
- PATH: adds npm global prefix dir at startup so globally-installed `claude.cmd` / `codex.cmd` are found
|
|
215
|
+
- `.cmd` wrappers (npm-installed CLIs) are auto-resolved to underlying `.js` scripts to bypass cmd.exe 8191-char argument limit
|
|
216
|
+
|
|
217
|
+
**Linux**
|
|
218
|
+
- `.deb` package (x64), installs to `/usr/local/lib/quoroom`
|
|
219
|
+
- Same shell wrapper and auto-update mechanism as macOS
|
|
220
|
+
|
|
221
|
+
</details>
|
|
222
|
+
|
|
200
223
|
### Uninstall
|
|
201
224
|
|
|
202
225
|
```bash
|
|
@@ -216,7 +239,7 @@ quoroom serve
|
|
|
216
239
|
|
|
217
240
|
If you installed with the macOS `.pkg` or Windows `.exe` installer, you can also use the launcher app/shortcut instead of command line.
|
|
218
241
|
|
|
219
|
-
On first run, `quoroom serve` automatically registers the Quoroom MCP server in every AI coding tool you have installed (Claude Code, Claude Desktop, Cursor, Windsurf). Just **restart your AI client once** — after that, all `mcp__quoroom__*` tools are available automatically in every session.
|
|
242
|
+
On first run, `quoroom serve` automatically registers the Quoroom MCP server in every AI coding tool you have installed (Claude Code, Claude Desktop, Codex, Cursor, Windsurf). Just **restart your AI client once** — after that, all `mcp__quoroom__*` tools are available automatically in every session.
|
|
220
243
|
|
|
221
244
|
Open **http://localhost:3700** (or the port shown in your terminal). The dashboard and API run locally, and your room data stays on your machine by default.
|
|
222
245
|
|
|
@@ -425,6 +448,10 @@ npm run typecheck # Type-check only (tsc --noEmit)
|
|
|
425
448
|
npm test # Run all tests (vitest, fork pool)
|
|
426
449
|
npm run test:watch # Watch mode
|
|
427
450
|
npm run test:e2e # End-to-end tests (Playwright)
|
|
451
|
+
|
|
452
|
+
# Windows
|
|
453
|
+
npm run dev:isolated:win # Windows equivalent of dev:isolated
|
|
454
|
+
npm run build:windows:local # Local Windows build (PowerShell)
|
|
428
455
|
```
|
|
429
456
|
|
|
430
457
|
### Docker (cloud runtime)
|
|
@@ -436,7 +463,13 @@ docker run -p 3700:3700 quoroom
|
|
|
436
463
|
|
|
437
464
|
## Releasing
|
|
438
465
|
|
|
439
|
-
|
|
466
|
+
Triggered by pushing a git tag (`v*`) → GitHub Actions multi-platform build:
|
|
467
|
+
|
|
468
|
+
- **macOS**: Universal `.pkg` (ARM64 + x64 via `lipo`), Swift tray app compiled, codesigned + Apple-notarized
|
|
469
|
+
- **Windows**: NSIS `.exe` installer, signed with SSL.com eSigner
|
|
470
|
+
- **Linux**: `.deb` package via `fpm`
|
|
471
|
+
- All platforms bundle Node.js runtime (no system dependency), with auto-update and crash rollback
|
|
472
|
+
- Post-build: GitHub Release → npm publish → Homebrew tap update
|
|
440
473
|
|
|
441
474
|
<details>
|
|
442
475
|
<summary>Project structure</summary>
|
|
@@ -480,6 +513,9 @@ room/
|
|
|
480
513
|
│ ├── embeddings.ts # Vector embeddings (all-MiniLM-L6-v2)
|
|
481
514
|
│ └── __tests__/ # Test suite (907 tests)
|
|
482
515
|
├── e2e/ # Playwright end-to-end tests
|
|
516
|
+
├── installers/
|
|
517
|
+
│ ├── macos/ # Swift tray app (QuoroomTray.swift)
|
|
518
|
+
│ └── windows/ # NSIS installer + PowerShell/VBS scripts
|
|
483
519
|
├── scripts/
|
|
484
520
|
│ └── build-mcp.js # esbuild bundling
|
|
485
521
|
├── Dockerfile # Cloud runtime image
|
|
@@ -492,16 +528,20 @@ room/
|
|
|
492
528
|
|
|
493
529
|
## Model Providers
|
|
494
530
|
|
|
495
|
-
Use your existing Claude
|
|
531
|
+
Use your existing Claude or ChatGPT subscription, or bring an API key.
|
|
532
|
+
|
|
533
|
+
| Model string | Provider | Execution | Requires |
|
|
534
|
+
|---|---|---|---|
|
|
535
|
+
| `claude` (default) | Claude Code CLI | Spawns CLI process | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed |
|
|
536
|
+
| `codex` | OpenAI Codex CLI | Spawns CLI process | `npm i -g @openai/codex` |
|
|
537
|
+
| `openai:gpt-4o-mini` | OpenAI API | HTTP REST | `OPENAI_API_KEY` |
|
|
538
|
+
| `anthropic:claude-3-5-sonnet-latest` | Anthropic API | HTTP REST | `ANTHROPIC_API_KEY` |
|
|
539
|
+
|
|
540
|
+
**CLI models** (`claude`, `codex`) — Full agentic loop with tool use via the CLI. Session continuity via `--resume`. On Windows, `.cmd` wrappers are auto-resolved to underlying `.js` scripts to bypass the cmd.exe 8191-char argument limit.
|
|
541
|
+
|
|
542
|
+
**API models** (`openai:*`, `anthropic:*`) — Direct HTTP calls. Support multi-turn tool-calling loops. API keys resolve from: room credentials → Clerk-saved keys → environment variables. `anthropic:*` also accepts the `claude-api:` prefix.
|
|
496
543
|
|
|
497
|
-
|
|
498
|
-
|------|----------|------|
|
|
499
|
-
| **Queen** | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Subscription |
|
|
500
|
-
| | Codex (ChatGPT) | Subscription |
|
|
501
|
-
| | OpenAI API | Pay-per-use |
|
|
502
|
-
| | Claude API | Pay-per-use |
|
|
503
|
-
| **Workers** | Inherit queen model | Depends on queen |
|
|
504
|
-
| | Claude (subscription or API) | Subscription / API |
|
|
544
|
+
Workers inherit the queen's model by default, or can use a separate API model.
|
|
505
545
|
|
|
506
546
|
## License
|
|
507
547
|
|
package/out/mcp/api-server.js
CHANGED
|
@@ -9914,7 +9914,7 @@ var require_package = __commonJS({
|
|
|
9914
9914
|
"package.json"(exports2, module2) {
|
|
9915
9915
|
module2.exports = {
|
|
9916
9916
|
name: "quoroom",
|
|
9917
|
-
version: "0.1.
|
|
9917
|
+
version: "0.1.28",
|
|
9918
9918
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
9919
9919
|
main: "./out/mcp/server.js",
|
|
9920
9920
|
bin: {
|
|
@@ -11343,7 +11343,6 @@ Rules:
|
|
|
11343
11343
|
var DEFAULT_ROOM_CONFIG = {
|
|
11344
11344
|
threshold: "majority",
|
|
11345
11345
|
timeoutMinutes: 60,
|
|
11346
|
-
keeperWeight: "dynamic",
|
|
11347
11346
|
tieBreaker: "queen",
|
|
11348
11347
|
autoApprove: ["low_impact"],
|
|
11349
11348
|
minCycleGapMs: 1e3,
|
|
@@ -12342,10 +12341,10 @@ function getRoomActivity(db2, roomId, limit = 50, eventTypes) {
|
|
|
12342
12341
|
const safeLimit = clampLimit(limit, 50, 500);
|
|
12343
12342
|
if (eventTypes && eventTypes.length > 0) {
|
|
12344
12343
|
const placeholders = eventTypes.map(() => "?").join(", ");
|
|
12345
|
-
const rows2 = db2.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC LIMIT ?`).all(roomId, ...eventTypes, safeLimit);
|
|
12344
|
+
const rows2 = db2.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC, id DESC LIMIT ?`).all(roomId, ...eventTypes, safeLimit);
|
|
12346
12345
|
return rows2.map(mapRoomActivityRow);
|
|
12347
12346
|
}
|
|
12348
|
-
const rows = db2.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC LIMIT ?").all(roomId, safeLimit);
|
|
12347
|
+
const rows = db2.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC, id DESC LIMIT ?").all(roomId, safeLimit);
|
|
12349
12348
|
return rows.map(mapRoomActivityRow);
|
|
12350
12349
|
}
|
|
12351
12350
|
function mapDecisionRow(row) {
|
|
@@ -12659,7 +12658,24 @@ function mapEscalationRow(row) {
|
|
|
12659
12658
|
}
|
|
12660
12659
|
function createEscalation(db2, roomId, fromAgentId, question, toAgentId) {
|
|
12661
12660
|
const result = db2.prepare("INSERT INTO escalations (room_id, from_agent_id, to_agent_id, question) VALUES (?, ?, ?, ?)").run(roomId, fromAgentId, toAgentId ?? null, question);
|
|
12662
|
-
|
|
12661
|
+
const escalation = getEscalation(db2, result.lastInsertRowid);
|
|
12662
|
+
const trimmedQuestion = question.trim();
|
|
12663
|
+
const detail = trimmedQuestion.length > 1e3 ? `${trimmedQuestion.slice(0, 1e3)}\u2026` : trimmedQuestion;
|
|
12664
|
+
let summary = "Message created";
|
|
12665
|
+
if (toAgentId == null) {
|
|
12666
|
+
summary = fromAgentId != null ? `Worker #${fromAgentId} sent message to keeper` : "Message sent to keeper";
|
|
12667
|
+
} else {
|
|
12668
|
+
summary = fromAgentId != null ? `Worker #${fromAgentId} sent message to worker #${toAgentId}` : `Keeper sent message to worker #${toAgentId}`;
|
|
12669
|
+
}
|
|
12670
|
+
logRoomActivity(
|
|
12671
|
+
db2,
|
|
12672
|
+
roomId,
|
|
12673
|
+
fromAgentId != null ? "worker" : "system",
|
|
12674
|
+
summary,
|
|
12675
|
+
detail || void 0,
|
|
12676
|
+
fromAgentId ?? void 0
|
|
12677
|
+
);
|
|
12678
|
+
return escalation;
|
|
12663
12679
|
}
|
|
12664
12680
|
function getEscalation(db2, id) {
|
|
12665
12681
|
const row = db2.prepare("SELECT * FROM escalations WHERE id = ?").get(id);
|
|
@@ -12682,7 +12698,24 @@ function listEscalations(db2, roomId, status2) {
|
|
|
12682
12698
|
return rows.map(mapEscalationRow);
|
|
12683
12699
|
}
|
|
12684
12700
|
function resolveEscalation(db2, id, answer) {
|
|
12701
|
+
const escalation = getEscalation(db2, id);
|
|
12685
12702
|
db2.prepare("UPDATE escalations SET answer = ?, status = 'resolved', resolved_at = datetime('now','localtime') WHERE id = ?").run(answer, id);
|
|
12703
|
+
if (!escalation) return;
|
|
12704
|
+
const trimmedAnswer = answer.trim();
|
|
12705
|
+
const detail = trimmedAnswer.length > 1e3 ? `${trimmedAnswer.slice(0, 1e3)}\u2026` : trimmedAnswer;
|
|
12706
|
+
let summary = "Message resolved";
|
|
12707
|
+
if (escalation.toAgentId == null && escalation.fromAgentId != null) {
|
|
12708
|
+
summary = `Keeper replied to worker #${escalation.fromAgentId}`;
|
|
12709
|
+
} else if (escalation.toAgentId != null) {
|
|
12710
|
+
summary = `Message resolved for worker #${escalation.toAgentId}`;
|
|
12711
|
+
}
|
|
12712
|
+
logRoomActivity(
|
|
12713
|
+
db2,
|
|
12714
|
+
escalation.roomId,
|
|
12715
|
+
"system",
|
|
12716
|
+
summary,
|
|
12717
|
+
detail || void 0
|
|
12718
|
+
);
|
|
12686
12719
|
}
|
|
12687
12720
|
function getRecentKeeperAnswers(db2, roomId, fromAgentId, limit = 5) {
|
|
12688
12721
|
const rows = db2.prepare(
|
|
@@ -13146,6 +13179,9 @@ function mapCycleLogRow(row) {
|
|
|
13146
13179
|
};
|
|
13147
13180
|
}
|
|
13148
13181
|
function createWorkerCycle(db2, workerId, roomId, model) {
|
|
13182
|
+
db2.prepare(
|
|
13183
|
+
"UPDATE worker_cycles SET status = 'failed', error_message = 'Superseded by newer cycle', finished_at = datetime('now','localtime') WHERE worker_id = ? AND status = 'running'"
|
|
13184
|
+
).run(workerId);
|
|
13149
13185
|
const result = db2.prepare(
|
|
13150
13186
|
"INSERT INTO worker_cycles (worker_id, room_id, model) VALUES (?, ?, ?)"
|
|
13151
13187
|
).run(workerId, roomId, model);
|
|
@@ -13167,7 +13203,7 @@ function completeWorkerCycle(db2, cycleId, errorMessage, usage) {
|
|
|
13167
13203
|
function listRoomCycles(db2, roomId, limit = 20) {
|
|
13168
13204
|
const safeLimit = clampLimit(limit, 20, 200);
|
|
13169
13205
|
const rows = db2.prepare(
|
|
13170
|
-
"SELECT * FROM worker_cycles WHERE room_id = ? ORDER BY started_at DESC LIMIT ?"
|
|
13206
|
+
"SELECT * FROM worker_cycles WHERE room_id = ? ORDER BY started_at DESC, id DESC LIMIT ?"
|
|
13171
13207
|
).all(roomId, safeLimit);
|
|
13172
13208
|
return rows.map(mapWorkerCycleRow);
|
|
13173
13209
|
}
|
|
@@ -24042,12 +24078,34 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
24042
24078
|
}
|
|
24043
24079
|
|
|
24044
24080
|
// src/shared/skills.ts
|
|
24081
|
+
var MAX_ACTIVE_SKILLS_PER_CYCLE = 8;
|
|
24082
|
+
var MAX_SKILL_CONTEXT_CHARS = 6e3;
|
|
24045
24083
|
function loadSkillsForAgent(db2, roomId, contextText) {
|
|
24046
24084
|
const skills = getActiveSkillsForContext(db2, roomId, contextText);
|
|
24047
24085
|
if (skills.length === 0) return "";
|
|
24048
|
-
|
|
24086
|
+
const sections = [];
|
|
24087
|
+
let used = 0;
|
|
24088
|
+
for (const skill of skills.slice(0, MAX_ACTIVE_SKILLS_PER_CYCLE)) {
|
|
24089
|
+
const prefix = sections.length > 0 ? "\n\n---\n\n" : "";
|
|
24090
|
+
const full = `${prefix}## Skill: ${skill.name}
|
|
24091
|
+
|
|
24092
|
+
${skill.content}`;
|
|
24093
|
+
const remaining = MAX_SKILL_CONTEXT_CHARS - used;
|
|
24094
|
+
if (remaining <= 0) break;
|
|
24095
|
+
if (full.length <= remaining) {
|
|
24096
|
+
sections.push(full);
|
|
24097
|
+
used += full.length;
|
|
24098
|
+
continue;
|
|
24099
|
+
}
|
|
24100
|
+
const clipped = full.slice(0, Math.max(0, remaining - 32)).trimEnd();
|
|
24101
|
+
if (clipped) {
|
|
24102
|
+
sections.push(`${clipped}
|
|
24049
24103
|
|
|
24050
|
-
|
|
24104
|
+
[truncated for cycle context]`);
|
|
24105
|
+
}
|
|
24106
|
+
break;
|
|
24107
|
+
}
|
|
24108
|
+
return sections.join("");
|
|
24051
24109
|
}
|
|
24052
24110
|
|
|
24053
24111
|
// src/shared/quorum.ts
|
|
@@ -24148,8 +24206,6 @@ function tally(db2, decisionId) {
|
|
|
24148
24206
|
return "rejected";
|
|
24149
24207
|
}
|
|
24150
24208
|
}
|
|
24151
|
-
const keeperWeightMode = room?.config.keeperWeight ?? "dynamic";
|
|
24152
|
-
const useWeighted = keeperWeightMode === "dynamic" && voters.length <= 1;
|
|
24153
24209
|
const queenWorkerId = room?.queenWorkerId ?? null;
|
|
24154
24210
|
const tieBreakerMode = room?.config.tieBreaker ?? "queen";
|
|
24155
24211
|
let yesWeight = 0;
|
|
@@ -24162,9 +24218,8 @@ function tally(db2, decisionId) {
|
|
|
24162
24218
|
}
|
|
24163
24219
|
const kv = decision.keeperVote;
|
|
24164
24220
|
if (kv && kv !== "abstain") {
|
|
24165
|
-
|
|
24166
|
-
if (kv === "
|
|
24167
|
-
else if (kv === "no") noWeight += keeperWeight;
|
|
24221
|
+
if (kv === "yes") yesWeight += 1;
|
|
24222
|
+
else if (kv === "no") noWeight += 1;
|
|
24168
24223
|
} else if (kv === "abstain") {
|
|
24169
24224
|
abstainCount++;
|
|
24170
24225
|
}
|
|
@@ -24191,9 +24246,7 @@ function tally(db2, decisionId) {
|
|
|
24191
24246
|
}
|
|
24192
24247
|
}
|
|
24193
24248
|
}
|
|
24194
|
-
const
|
|
24195
|
-
const noDisplay = useWeighted ? noWeight.toFixed(2) : String(noWeight);
|
|
24196
|
-
const result = `Yes: ${yesDisplay}, No: ${noDisplay}, Abstain: ${abstainCount}` + (useWeighted ? " (weighted)" : "");
|
|
24249
|
+
const result = `Yes: ${yesWeight}, No: ${noWeight}, Abstain: ${abstainCount}`;
|
|
24197
24250
|
resolveDecision(db2, decisionId, status2, result);
|
|
24198
24251
|
logRoomActivity(
|
|
24199
24252
|
db2,
|
|
@@ -25736,6 +25789,9 @@ function checkRateLimit(result) {
|
|
|
25736
25789
|
sessionId: result.sessionId
|
|
25737
25790
|
});
|
|
25738
25791
|
}
|
|
25792
|
+
function isCliContextOverflowError(message) {
|
|
25793
|
+
return /compact|compaction|context.*(window|limit|overflow|too large)|model_visible_bytes|token.*limit.*exceed/i.test(message);
|
|
25794
|
+
}
|
|
25739
25795
|
async function runCycle(db2, roomId, worker, maxTurns, options) {
|
|
25740
25796
|
logRoomActivity(
|
|
25741
25797
|
db2,
|
|
@@ -25820,14 +25876,22 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
|
|
|
25820
25876
|
${skillContent}` : ""
|
|
25821
25877
|
].join("");
|
|
25822
25878
|
const isCli = model === "claude" || model.startsWith("claude-") || model === "codex";
|
|
25879
|
+
const CLI_SESSION_MAX_TURNS = 20;
|
|
25823
25880
|
let resumeSessionId;
|
|
25824
25881
|
let previousMessages;
|
|
25825
25882
|
const agentSession = getAgentSession(db2, worker.id);
|
|
25826
25883
|
if (agentSession) {
|
|
25827
25884
|
const updatedAt = new Date(agentSession.updatedAt);
|
|
25828
25885
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
|
|
25829
|
-
|
|
25886
|
+
const cliSessionTooLong = isCli && !!agentSession.sessionId && agentSession.turnCount >= CLI_SESSION_MAX_TURNS;
|
|
25887
|
+
if (updatedAt < sevenDaysAgo || agentSession.model !== model || cliSessionTooLong) {
|
|
25830
25888
|
deleteAgentSession(db2, worker.id);
|
|
25889
|
+
if (cliSessionTooLong) {
|
|
25890
|
+
logBuffer2.addSynthetic(
|
|
25891
|
+
"system",
|
|
25892
|
+
`Session rotated after ${agentSession.turnCount} cycles to avoid context overflow`
|
|
25893
|
+
);
|
|
25894
|
+
}
|
|
25831
25895
|
} else if (isCli && agentSession.sessionId) {
|
|
25832
25896
|
resumeSessionId = agentSession.sessionId;
|
|
25833
25897
|
} else if (!isCli && agentSession.messagesJson) {
|
|
@@ -26157,7 +26221,7 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
26157
26221
|
return result2.content;
|
|
26158
26222
|
}
|
|
26159
26223
|
} : {};
|
|
26160
|
-
const
|
|
26224
|
+
const executeWithSession = (sessionId) => executeAgent({
|
|
26161
26225
|
model,
|
|
26162
26226
|
prompt,
|
|
26163
26227
|
systemPrompt,
|
|
@@ -26170,7 +26234,7 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
26170
26234
|
// CLI models: bypass permission prompts for headless operation
|
|
26171
26235
|
permissionMode: isCli ? "bypassPermissions" : void 0,
|
|
26172
26236
|
// CLI models: pass resumeSessionId for native --resume
|
|
26173
|
-
resumeSessionId,
|
|
26237
|
+
resumeSessionId: sessionId,
|
|
26174
26238
|
// API models: pass conversation history + persistence callback
|
|
26175
26239
|
previousMessages: isCli ? void 0 : previousMessages,
|
|
26176
26240
|
onSessionUpdate: isCli ? void 0 : (msgs) => {
|
|
@@ -26179,6 +26243,16 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
26179
26243
|
},
|
|
26180
26244
|
...apiToolOpts
|
|
26181
26245
|
});
|
|
26246
|
+
let result = await executeWithSession(resumeSessionId);
|
|
26247
|
+
if (isCli && result.exitCode !== 0) {
|
|
26248
|
+
const failure = result.output?.trim() || "";
|
|
26249
|
+
if (isCliContextOverflowError(failure)) {
|
|
26250
|
+
deleteAgentSession(db2, worker.id);
|
|
26251
|
+
logBuffer2.addSynthetic("system", "Session overflow detected \u2014 retrying this cycle with a fresh session");
|
|
26252
|
+
logBuffer2.flush();
|
|
26253
|
+
result = await executeWithSession(void 0);
|
|
26254
|
+
}
|
|
26255
|
+
}
|
|
26182
26256
|
const rateLimitInfo = checkRateLimit(result);
|
|
26183
26257
|
if (rateLimitInfo) {
|
|
26184
26258
|
throw new RateLimitError(rateLimitInfo);
|
|
@@ -26198,6 +26272,13 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
26198
26272
|
worker.id
|
|
26199
26273
|
);
|
|
26200
26274
|
updateAgentState(db2, worker.id, "idle");
|
|
26275
|
+
if (isCli) {
|
|
26276
|
+
if (isCliContextOverflowError(errorDetail)) {
|
|
26277
|
+
deleteAgentSession(db2, worker.id);
|
|
26278
|
+
logBuffer2.addSynthetic("system", "Session reset due to context overflow \u2014 next cycle will start fresh");
|
|
26279
|
+
logBuffer2.flush();
|
|
26280
|
+
}
|
|
26281
|
+
}
|
|
26201
26282
|
return result.output;
|
|
26202
26283
|
}
|
|
26203
26284
|
if (isCli && result.sessionId) {
|
|
@@ -27964,6 +28045,12 @@ function isRateLimitFailure(message) {
|
|
|
27964
28045
|
const lower = message.toLowerCase();
|
|
27965
28046
|
return lower.includes("rate limit") || lower.includes("limit reached") || lower.includes("too many requests") || lower.includes("insufficient_quota") || lower.includes("quota") || lower.includes("429") || lower.includes("limit") && lower.includes("reset");
|
|
27966
28047
|
}
|
|
28048
|
+
function isTransientFailure(message, timedOut) {
|
|
28049
|
+
if (timedOut) return true;
|
|
28050
|
+
if (isRateLimitFailure(message)) return true;
|
|
28051
|
+
const lower = message.toLowerCase();
|
|
28052
|
+
return lower.includes("timed out") || lower.includes("timeout") || lower.includes("temporarily unavailable") || lower.includes("service unavailable") || lower.includes("connection reset") || lower.includes("socket hang up") || lower.includes("econnreset") || lower.includes("etimedout") || lower.includes("eai_again");
|
|
28053
|
+
}
|
|
27967
28054
|
async function executeClerkWithFallback(options) {
|
|
27968
28055
|
const preferred = options.preferredModel?.trim() || DEFAULT_CLERK_MODEL;
|
|
27969
28056
|
const candidates = buildExecutionCandidates(options.db, preferred);
|
|
@@ -28002,7 +28089,7 @@ async function executeClerkWithFallback(options) {
|
|
|
28002
28089
|
const error = deriveExecutionFailure(result, candidate.model);
|
|
28003
28090
|
attempts.push({ model: candidate.model, error });
|
|
28004
28091
|
const hasMoreCandidates = i < candidates.length - 1;
|
|
28005
|
-
if (!hasMoreCandidates || !
|
|
28092
|
+
if (!hasMoreCandidates || !isTransientFailure(error, result.timedOut)) {
|
|
28006
28093
|
return {
|
|
28007
28094
|
ok: false,
|
|
28008
28095
|
model: candidate.model,
|
|
@@ -29995,10 +30082,22 @@ function registerClerkRoutes(router) {
|
|
|
29995
30082
|
init_cloud_sync();
|
|
29996
30083
|
var CLERK_NOTIFY_EMAIL_KEY = "clerk_notify_email";
|
|
29997
30084
|
var CLERK_NOTIFY_TELEGRAM_KEY = "clerk_notify_telegram";
|
|
30085
|
+
var CLERK_NOTIFY_MIN_INTERVAL_MINUTES_KEY = "clerk_notify_min_interval_minutes";
|
|
30086
|
+
var CLERK_NOTIFY_URGENT_MIN_INTERVAL_MINUTES_KEY = "clerk_notify_urgent_min_interval_minutes";
|
|
29998
30087
|
var CLERK_NOTIFY_ESCALATION_CURSOR_KEY = "clerk_notify_escalation_cursor";
|
|
29999
30088
|
var CLERK_NOTIFY_DECISION_CURSOR_KEY = "clerk_notify_decision_cursor";
|
|
30000
30089
|
var CLERK_NOTIFY_ROOM_MESSAGE_CURSOR_KEY = "clerk_notify_room_message_cursor";
|
|
30001
30090
|
var CLERK_NOTIFY_DIGEST_STYLE_CURSOR_KEY = "clerk_notify_digest_style_cursor";
|
|
30091
|
+
var CLERK_NOTIFY_LAST_SENT_AT_KEY = "clerk_notify_last_sent_at";
|
|
30092
|
+
var DEFAULT_NOTIFY_MIN_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
30093
|
+
var DEFAULT_NOTIFY_URGENT_MIN_INTERVAL_MS = 60 * 60 * 1e3;
|
|
30094
|
+
var MIN_NOTIFY_INTERVAL_MS = 30 * 60 * 1e3;
|
|
30095
|
+
var MAX_NOTIFY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
30096
|
+
var MIN_URGENT_INTERVAL_MS = 10 * 60 * 1e3;
|
|
30097
|
+
var MAX_URGENT_INTERVAL_MS = 12 * 60 * 60 * 1e3;
|
|
30098
|
+
var URGENT_ESCALATION_THRESHOLD = 6;
|
|
30099
|
+
var URGENT_DECISION_THRESHOLD = 4;
|
|
30100
|
+
var URGENT_TOTAL_THRESHOLD = 12;
|
|
30002
30101
|
var DIGEST_OPENERS = [
|
|
30003
30102
|
(count) => `I need your call on ${count} item${count === 1 ? "" : "s"}.`,
|
|
30004
30103
|
(count) => `${count} decision point${count === 1 ? "" : "s"} need${count === 1 ? "s" : ""} your direction right now.`,
|
|
@@ -30036,6 +30135,20 @@ function parseCursor(raw) {
|
|
|
30036
30135
|
if (!Number.isFinite(parsed) || parsed < 0) return 0;
|
|
30037
30136
|
return parsed;
|
|
30038
30137
|
}
|
|
30138
|
+
function parseIsoMs3(raw) {
|
|
30139
|
+
const value = String(raw ?? "").trim();
|
|
30140
|
+
if (!value) return null;
|
|
30141
|
+
const parsed = Date.parse(value);
|
|
30142
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
30143
|
+
}
|
|
30144
|
+
function resolveIntervalMs(db2, key, fallbackMs, minMs, maxMs) {
|
|
30145
|
+
const raw = (getSetting(db2, key) ?? "").trim();
|
|
30146
|
+
if (!raw) return fallbackMs;
|
|
30147
|
+
const minutes = Number.parseInt(raw, 10);
|
|
30148
|
+
if (!Number.isFinite(minutes) || minutes < 0) return fallbackMs;
|
|
30149
|
+
if (minutes === 0) return 0;
|
|
30150
|
+
return Math.min(maxMs, Math.max(minMs, minutes * 6e4));
|
|
30151
|
+
}
|
|
30039
30152
|
function getKeeperUserNumber2(db2) {
|
|
30040
30153
|
const raw = (getSetting(db2, "keeper_user_number") ?? "").trim();
|
|
30041
30154
|
if (!/^\d{5,6}$/.test(raw)) return null;
|
|
@@ -30183,6 +30296,20 @@ function buildKeeperDigest(db2, input, styleCursor) {
|
|
|
30183
30296
|
);
|
|
30184
30297
|
return lines.join("\n");
|
|
30185
30298
|
}
|
|
30299
|
+
function getPendingDigestCounts(input) {
|
|
30300
|
+
const escalations = input.escalations.length;
|
|
30301
|
+
const decisions = input.decisions.length;
|
|
30302
|
+
const roomMessages = input.roomMessages.length;
|
|
30303
|
+
return {
|
|
30304
|
+
escalations,
|
|
30305
|
+
decisions,
|
|
30306
|
+
roomMessages,
|
|
30307
|
+
total: escalations + decisions + roomMessages
|
|
30308
|
+
};
|
|
30309
|
+
}
|
|
30310
|
+
function isUrgentDigest(counts) {
|
|
30311
|
+
return counts.escalations >= URGENT_ESCALATION_THRESHOLD || counts.decisions >= URGENT_DECISION_THRESHOLD || counts.total >= URGENT_TOTAL_THRESHOLD;
|
|
30312
|
+
}
|
|
30186
30313
|
async function relayPendingKeeperRequests(db2) {
|
|
30187
30314
|
if (isCloudDeployment()) return;
|
|
30188
30315
|
const channels = getClerkPreferredNotifyChannels(db2);
|
|
@@ -30215,6 +30342,32 @@ async function relayPendingKeeperRequests(db2) {
|
|
|
30215
30342
|
roomMessages: pendingRoomMessages
|
|
30216
30343
|
}, digestStyleCursor);
|
|
30217
30344
|
if (!content) return;
|
|
30345
|
+
const pendingCounts = getPendingDigestCounts({
|
|
30346
|
+
escalations: pendingEscalations,
|
|
30347
|
+
decisions: pendingDecisions,
|
|
30348
|
+
roomMessages: pendingRoomMessages
|
|
30349
|
+
});
|
|
30350
|
+
const minIntervalMs = resolveIntervalMs(
|
|
30351
|
+
db2,
|
|
30352
|
+
CLERK_NOTIFY_MIN_INTERVAL_MINUTES_KEY,
|
|
30353
|
+
DEFAULT_NOTIFY_MIN_INTERVAL_MS,
|
|
30354
|
+
MIN_NOTIFY_INTERVAL_MS,
|
|
30355
|
+
MAX_NOTIFY_INTERVAL_MS
|
|
30356
|
+
);
|
|
30357
|
+
const urgentIntervalMs = resolveIntervalMs(
|
|
30358
|
+
db2,
|
|
30359
|
+
CLERK_NOTIFY_URGENT_MIN_INTERVAL_MINUTES_KEY,
|
|
30360
|
+
DEFAULT_NOTIFY_URGENT_MIN_INTERVAL_MS,
|
|
30361
|
+
MIN_URGENT_INTERVAL_MS,
|
|
30362
|
+
MAX_URGENT_INTERVAL_MS
|
|
30363
|
+
);
|
|
30364
|
+
const lastSentMs = parseIsoMs3(getSetting(db2, CLERK_NOTIFY_LAST_SENT_AT_KEY));
|
|
30365
|
+
if (lastSentMs != null) {
|
|
30366
|
+
const elapsedMs = Date.now() - lastSentMs;
|
|
30367
|
+
const regularAllowed = minIntervalMs <= 0 || elapsedMs >= minIntervalMs;
|
|
30368
|
+
const urgentAllowed = isUrgentDigest(pendingCounts) && (urgentIntervalMs <= 0 || elapsedMs >= urgentIntervalMs);
|
|
30369
|
+
if (!regularAllowed && !urgentAllowed) return;
|
|
30370
|
+
}
|
|
30218
30371
|
const sent = await sendClerkAlert(db2, { content });
|
|
30219
30372
|
if (!sent) return;
|
|
30220
30373
|
const nextEscalationCursor = pendingEscalations.reduce((max, item) => Math.max(max, item.escalation?.id ?? 0), escalationCursor);
|
|
@@ -30224,6 +30377,7 @@ async function relayPendingKeeperRequests(db2) {
|
|
|
30224
30377
|
setSetting(db2, CLERK_NOTIFY_DECISION_CURSOR_KEY, String(nextDecisionCursor));
|
|
30225
30378
|
setSetting(db2, CLERK_NOTIFY_ROOM_MESSAGE_CURSOR_KEY, String(nextRoomMessageCursor));
|
|
30226
30379
|
setSetting(db2, CLERK_NOTIFY_DIGEST_STYLE_CURSOR_KEY, String(digestStyleCursor + 1));
|
|
30380
|
+
setSetting(db2, CLERK_NOTIFY_LAST_SENT_AT_KEY, (/* @__PURE__ */ new Date()).toISOString());
|
|
30227
30381
|
const byRoom = /* @__PURE__ */ new Map();
|
|
30228
30382
|
for (const item of pendingEscalations) {
|
|
30229
30383
|
if (!item.escalation) continue;
|
|
@@ -32957,7 +33111,7 @@ function semverGt(a, b) {
|
|
|
32957
33111
|
}
|
|
32958
33112
|
function getCurrentVersion() {
|
|
32959
33113
|
try {
|
|
32960
|
-
return true ? "0.1.
|
|
33114
|
+
return true ? "0.1.28" : null.version;
|
|
32961
33115
|
} catch {
|
|
32962
33116
|
return "0.0.0";
|
|
32963
33117
|
}
|
|
@@ -33116,7 +33270,7 @@ var cachedVersion = null;
|
|
|
33116
33270
|
function getVersion3() {
|
|
33117
33271
|
if (cachedVersion) return cachedVersion;
|
|
33118
33272
|
try {
|
|
33119
|
-
cachedVersion = true ? "0.1.
|
|
33273
|
+
cachedVersion = true ? "0.1.28" : null.version;
|
|
33120
33274
|
} catch {
|
|
33121
33275
|
cachedVersion = "unknown";
|
|
33122
33276
|
}
|
package/out/mcp/cli.js
CHANGED
|
@@ -22241,7 +22241,6 @@ Rules:
|
|
|
22241
22241
|
DEFAULT_ROOM_CONFIG = {
|
|
22242
22242
|
threshold: "majority",
|
|
22243
22243
|
timeoutMinutes: 60,
|
|
22244
|
-
keeperWeight: "dynamic",
|
|
22245
22244
|
tieBreaker: "queen",
|
|
22246
22245
|
autoApprove: ["low_impact"],
|
|
22247
22246
|
minCycleGapMs: 1e3,
|
|
@@ -23450,10 +23449,10 @@ function getRoomActivity(db3, roomId, limit = 50, eventTypes) {
|
|
|
23450
23449
|
const safeLimit = clampLimit(limit, 50, 500);
|
|
23451
23450
|
if (eventTypes && eventTypes.length > 0) {
|
|
23452
23451
|
const placeholders = eventTypes.map(() => "?").join(", ");
|
|
23453
|
-
const rows2 = db3.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC LIMIT ?`).all(roomId, ...eventTypes, safeLimit);
|
|
23452
|
+
const rows2 = db3.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC, id DESC LIMIT ?`).all(roomId, ...eventTypes, safeLimit);
|
|
23454
23453
|
return rows2.map(mapRoomActivityRow);
|
|
23455
23454
|
}
|
|
23456
|
-
const rows = db3.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC LIMIT ?").all(roomId, safeLimit);
|
|
23455
|
+
const rows = db3.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC, id DESC LIMIT ?").all(roomId, safeLimit);
|
|
23457
23456
|
return rows.map(mapRoomActivityRow);
|
|
23458
23457
|
}
|
|
23459
23458
|
function mapDecisionRow(row) {
|
|
@@ -23783,7 +23782,24 @@ function mapEscalationRow(row) {
|
|
|
23783
23782
|
}
|
|
23784
23783
|
function createEscalation(db3, roomId, fromAgentId, question, toAgentId) {
|
|
23785
23784
|
const result = db3.prepare("INSERT INTO escalations (room_id, from_agent_id, to_agent_id, question) VALUES (?, ?, ?, ?)").run(roomId, fromAgentId, toAgentId ?? null, question);
|
|
23786
|
-
|
|
23785
|
+
const escalation = getEscalation(db3, result.lastInsertRowid);
|
|
23786
|
+
const trimmedQuestion = question.trim();
|
|
23787
|
+
const detail = trimmedQuestion.length > 1e3 ? `${trimmedQuestion.slice(0, 1e3)}\u2026` : trimmedQuestion;
|
|
23788
|
+
let summary = "Message created";
|
|
23789
|
+
if (toAgentId == null) {
|
|
23790
|
+
summary = fromAgentId != null ? `Worker #${fromAgentId} sent message to keeper` : "Message sent to keeper";
|
|
23791
|
+
} else {
|
|
23792
|
+
summary = fromAgentId != null ? `Worker #${fromAgentId} sent message to worker #${toAgentId}` : `Keeper sent message to worker #${toAgentId}`;
|
|
23793
|
+
}
|
|
23794
|
+
logRoomActivity(
|
|
23795
|
+
db3,
|
|
23796
|
+
roomId,
|
|
23797
|
+
fromAgentId != null ? "worker" : "system",
|
|
23798
|
+
summary,
|
|
23799
|
+
detail || void 0,
|
|
23800
|
+
fromAgentId ?? void 0
|
|
23801
|
+
);
|
|
23802
|
+
return escalation;
|
|
23787
23803
|
}
|
|
23788
23804
|
function getEscalation(db3, id) {
|
|
23789
23805
|
const row = db3.prepare("SELECT * FROM escalations WHERE id = ?").get(id);
|
|
@@ -23806,7 +23822,24 @@ function listEscalations(db3, roomId, status2) {
|
|
|
23806
23822
|
return rows.map(mapEscalationRow);
|
|
23807
23823
|
}
|
|
23808
23824
|
function resolveEscalation(db3, id, answer) {
|
|
23825
|
+
const escalation = getEscalation(db3, id);
|
|
23809
23826
|
db3.prepare("UPDATE escalations SET answer = ?, status = 'resolved', resolved_at = datetime('now','localtime') WHERE id = ?").run(answer, id);
|
|
23827
|
+
if (!escalation) return;
|
|
23828
|
+
const trimmedAnswer = answer.trim();
|
|
23829
|
+
const detail = trimmedAnswer.length > 1e3 ? `${trimmedAnswer.slice(0, 1e3)}\u2026` : trimmedAnswer;
|
|
23830
|
+
let summary = "Message resolved";
|
|
23831
|
+
if (escalation.toAgentId == null && escalation.fromAgentId != null) {
|
|
23832
|
+
summary = `Keeper replied to worker #${escalation.fromAgentId}`;
|
|
23833
|
+
} else if (escalation.toAgentId != null) {
|
|
23834
|
+
summary = `Message resolved for worker #${escalation.toAgentId}`;
|
|
23835
|
+
}
|
|
23836
|
+
logRoomActivity(
|
|
23837
|
+
db3,
|
|
23838
|
+
escalation.roomId,
|
|
23839
|
+
"system",
|
|
23840
|
+
summary,
|
|
23841
|
+
detail || void 0
|
|
23842
|
+
);
|
|
23810
23843
|
}
|
|
23811
23844
|
function getRecentKeeperAnswers(db3, roomId, fromAgentId, limit = 5) {
|
|
23812
23845
|
const rows = db3.prepare(
|
|
@@ -24332,6 +24365,9 @@ function mapCycleLogRow(row) {
|
|
|
24332
24365
|
};
|
|
24333
24366
|
}
|
|
24334
24367
|
function createWorkerCycle(db3, workerId, roomId, model) {
|
|
24368
|
+
db3.prepare(
|
|
24369
|
+
"UPDATE worker_cycles SET status = 'failed', error_message = 'Superseded by newer cycle', finished_at = datetime('now','localtime') WHERE worker_id = ? AND status = 'running'"
|
|
24370
|
+
).run(workerId);
|
|
24335
24371
|
const result = db3.prepare(
|
|
24336
24372
|
"INSERT INTO worker_cycles (worker_id, room_id, model) VALUES (?, ?, ?)"
|
|
24337
24373
|
).run(workerId, roomId, model);
|
|
@@ -24353,7 +24389,7 @@ function completeWorkerCycle(db3, cycleId, errorMessage, usage) {
|
|
|
24353
24389
|
function listRoomCycles(db3, roomId, limit = 20) {
|
|
24354
24390
|
const safeLimit = clampLimit(limit, 20, 200);
|
|
24355
24391
|
const rows = db3.prepare(
|
|
24356
|
-
"SELECT * FROM worker_cycles WHERE room_id = ? ORDER BY started_at DESC LIMIT ?"
|
|
24392
|
+
"SELECT * FROM worker_cycles WHERE room_id = ? ORDER BY started_at DESC, id DESC LIMIT ?"
|
|
24357
24393
|
).all(roomId, safeLimit);
|
|
24358
24394
|
return rows.map(mapWorkerCycleRow);
|
|
24359
24395
|
}
|
|
@@ -49538,7 +49574,6 @@ function registerRoomTools(server) {
|
|
|
49538
49574
|
maxConcurrentTasks: external_exports.number().int().min(1).max(10).optional().describe("Maximum parallel tasks (1\u201310)"),
|
|
49539
49575
|
threshold: external_exports.enum(["majority", "supermajority", "unanimous"]).optional().describe("Voting threshold for quorum decisions"),
|
|
49540
49576
|
autoApprove: external_exports.array(external_exports.string()).optional().describe('Decision types to auto-approve without voting (e.g. ["low_impact"])'),
|
|
49541
|
-
keeperWeight: external_exports.enum(["dynamic", "equal"]).optional().describe("Keeper vote weighting: dynamic (51% in small rooms) or equal"),
|
|
49542
49577
|
tieBreaker: external_exports.enum(["queen", "none"]).optional().describe("Tie-breaking strategy: queen vote decides, or reject on tie"),
|
|
49543
49578
|
minVoters: external_exports.number().int().min(0).max(100).optional().describe("Minimum non-abstain votes required for a decision to pass (0 = no minimum)"),
|
|
49544
49579
|
sealedBallot: external_exports.boolean().optional().describe("When true, individual votes are hidden until the decision resolves"),
|
|
@@ -49553,7 +49588,6 @@ function registerRoomTools(server) {
|
|
|
49553
49588
|
maxConcurrentTasks,
|
|
49554
49589
|
threshold,
|
|
49555
49590
|
autoApprove,
|
|
49556
|
-
keeperWeight,
|
|
49557
49591
|
tieBreaker,
|
|
49558
49592
|
minVoters,
|
|
49559
49593
|
sealedBallot,
|
|
@@ -49588,10 +49622,6 @@ function registerRoomTools(server) {
|
|
|
49588
49622
|
configUpdates.autoApprove = autoApprove;
|
|
49589
49623
|
changes.push(`autoApprove: [${room.config.autoApprove.join(",")}] \u2192 [${autoApprove.join(",")}]`);
|
|
49590
49624
|
}
|
|
49591
|
-
if (keeperWeight !== void 0) {
|
|
49592
|
-
configUpdates.keeperWeight = keeperWeight;
|
|
49593
|
-
changes.push(`keeperWeight: ${room.config.keeperWeight} \u2192 ${keeperWeight}`);
|
|
49594
|
-
}
|
|
49595
49625
|
if (tieBreaker !== void 0) {
|
|
49596
49626
|
configUpdates.tieBreaker = tieBreaker;
|
|
49597
49627
|
changes.push(`tieBreaker: ${room.config.tieBreaker} \u2192 ${tieBreaker}`);
|
|
@@ -49743,8 +49773,6 @@ function tally(db3, decisionId) {
|
|
|
49743
49773
|
return "rejected";
|
|
49744
49774
|
}
|
|
49745
49775
|
}
|
|
49746
|
-
const keeperWeightMode = room?.config.keeperWeight ?? "dynamic";
|
|
49747
|
-
const useWeighted = keeperWeightMode === "dynamic" && voters.length <= 1;
|
|
49748
49776
|
const queenWorkerId = room?.queenWorkerId ?? null;
|
|
49749
49777
|
const tieBreakerMode = room?.config.tieBreaker ?? "queen";
|
|
49750
49778
|
let yesWeight = 0;
|
|
@@ -49757,9 +49785,8 @@ function tally(db3, decisionId) {
|
|
|
49757
49785
|
}
|
|
49758
49786
|
const kv = decision.keeperVote;
|
|
49759
49787
|
if (kv && kv !== "abstain") {
|
|
49760
|
-
|
|
49761
|
-
if (kv === "
|
|
49762
|
-
else if (kv === "no") noWeight += keeperWeight;
|
|
49788
|
+
if (kv === "yes") yesWeight += 1;
|
|
49789
|
+
else if (kv === "no") noWeight += 1;
|
|
49763
49790
|
} else if (kv === "abstain") {
|
|
49764
49791
|
abstainCount++;
|
|
49765
49792
|
}
|
|
@@ -49786,9 +49813,7 @@ function tally(db3, decisionId) {
|
|
|
49786
49813
|
}
|
|
49787
49814
|
}
|
|
49788
49815
|
}
|
|
49789
|
-
const
|
|
49790
|
-
const noDisplay = useWeighted ? noWeight.toFixed(2) : String(noWeight);
|
|
49791
|
-
const result = `Yes: ${yesDisplay}, No: ${noDisplay}, Abstain: ${abstainCount}` + (useWeighted ? " (weighted)" : "");
|
|
49816
|
+
const result = `Yes: ${yesWeight}, No: ${noWeight}, Abstain: ${abstainCount}`;
|
|
49792
49817
|
resolveDecision(db3, decisionId, status2, result);
|
|
49793
49818
|
logRoomActivity(
|
|
49794
49819
|
db3,
|
|
@@ -50313,9 +50338,29 @@ var init_self_mod = __esm({
|
|
|
50313
50338
|
function loadSkillsForAgent(db3, roomId, contextText) {
|
|
50314
50339
|
const skills = getActiveSkillsForContext(db3, roomId, contextText);
|
|
50315
50340
|
if (skills.length === 0) return "";
|
|
50316
|
-
|
|
50341
|
+
const sections = [];
|
|
50342
|
+
let used = 0;
|
|
50343
|
+
for (const skill of skills.slice(0, MAX_ACTIVE_SKILLS_PER_CYCLE)) {
|
|
50344
|
+
const prefix = sections.length > 0 ? "\n\n---\n\n" : "";
|
|
50345
|
+
const full = `${prefix}## Skill: ${skill.name}
|
|
50346
|
+
|
|
50347
|
+
${skill.content}`;
|
|
50348
|
+
const remaining = MAX_SKILL_CONTEXT_CHARS - used;
|
|
50349
|
+
if (remaining <= 0) break;
|
|
50350
|
+
if (full.length <= remaining) {
|
|
50351
|
+
sections.push(full);
|
|
50352
|
+
used += full.length;
|
|
50353
|
+
continue;
|
|
50354
|
+
}
|
|
50355
|
+
const clipped = full.slice(0, Math.max(0, remaining - 32)).trimEnd();
|
|
50356
|
+
if (clipped) {
|
|
50357
|
+
sections.push(`${clipped}
|
|
50317
50358
|
|
|
50318
|
-
|
|
50359
|
+
[truncated for cycle context]`);
|
|
50360
|
+
}
|
|
50361
|
+
break;
|
|
50362
|
+
}
|
|
50363
|
+
return sections.join("");
|
|
50319
50364
|
}
|
|
50320
50365
|
function createAgentSkill(db3, roomId, workerId, name, content, activationContext) {
|
|
50321
50366
|
return createSkill(db3, roomId, name, content, {
|
|
@@ -50329,10 +50374,13 @@ function incrementSkillVersion(db3, skillId) {
|
|
|
50329
50374
|
if (!skill) throw new Error(`Skill ${skillId} not found`);
|
|
50330
50375
|
updateSkill(db3, skillId, { version: skill.version + 1 });
|
|
50331
50376
|
}
|
|
50377
|
+
var MAX_ACTIVE_SKILLS_PER_CYCLE, MAX_SKILL_CONTEXT_CHARS;
|
|
50332
50378
|
var init_skills = __esm({
|
|
50333
50379
|
"src/shared/skills.ts"() {
|
|
50334
50380
|
"use strict";
|
|
50335
50381
|
init_db_queries();
|
|
50382
|
+
MAX_ACTIVE_SKILLS_PER_CYCLE = 8;
|
|
50383
|
+
MAX_SKILL_CONTEXT_CHARS = 6e3;
|
|
50336
50384
|
}
|
|
50337
50385
|
});
|
|
50338
50386
|
|
|
@@ -52489,7 +52537,7 @@ var server_exports = {};
|
|
|
52489
52537
|
async function main() {
|
|
52490
52538
|
const server = new McpServer({
|
|
52491
52539
|
name: "quoroom",
|
|
52492
|
-
version: true ? "0.1.
|
|
52540
|
+
version: true ? "0.1.28" : "0.0.0"
|
|
52493
52541
|
});
|
|
52494
52542
|
registerMemoryTools(server);
|
|
52495
52543
|
registerSchedulerTools(server);
|
|
@@ -53986,6 +54034,9 @@ function checkRateLimit(result) {
|
|
|
53986
54034
|
sessionId: result.sessionId
|
|
53987
54035
|
});
|
|
53988
54036
|
}
|
|
54037
|
+
function isCliContextOverflowError(message) {
|
|
54038
|
+
return /compact|compaction|context.*(window|limit|overflow|too large)|model_visible_bytes|token.*limit.*exceed/i.test(message);
|
|
54039
|
+
}
|
|
53989
54040
|
async function runCycle(db3, roomId, worker, maxTurns, options) {
|
|
53990
54041
|
logRoomActivity(
|
|
53991
54042
|
db3,
|
|
@@ -54070,14 +54121,22 @@ async function runCycle(db3, roomId, worker, maxTurns, options) {
|
|
|
54070
54121
|
${skillContent}` : ""
|
|
54071
54122
|
].join("");
|
|
54072
54123
|
const isCli = model === "claude" || model.startsWith("claude-") || model === "codex";
|
|
54124
|
+
const CLI_SESSION_MAX_TURNS = 20;
|
|
54073
54125
|
let resumeSessionId;
|
|
54074
54126
|
let previousMessages;
|
|
54075
54127
|
const agentSession = getAgentSession(db3, worker.id);
|
|
54076
54128
|
if (agentSession) {
|
|
54077
54129
|
const updatedAt = new Date(agentSession.updatedAt);
|
|
54078
54130
|
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
|
|
54079
|
-
|
|
54131
|
+
const cliSessionTooLong = isCli && !!agentSession.sessionId && agentSession.turnCount >= CLI_SESSION_MAX_TURNS;
|
|
54132
|
+
if (updatedAt < sevenDaysAgo || agentSession.model !== model || cliSessionTooLong) {
|
|
54080
54133
|
deleteAgentSession(db3, worker.id);
|
|
54134
|
+
if (cliSessionTooLong) {
|
|
54135
|
+
logBuffer2.addSynthetic(
|
|
54136
|
+
"system",
|
|
54137
|
+
`Session rotated after ${agentSession.turnCount} cycles to avoid context overflow`
|
|
54138
|
+
);
|
|
54139
|
+
}
|
|
54081
54140
|
} else if (isCli && agentSession.sessionId) {
|
|
54082
54141
|
resumeSessionId = agentSession.sessionId;
|
|
54083
54142
|
} else if (!isCli && agentSession.messagesJson) {
|
|
@@ -54407,7 +54466,7 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
54407
54466
|
return result2.content;
|
|
54408
54467
|
}
|
|
54409
54468
|
} : {};
|
|
54410
|
-
const
|
|
54469
|
+
const executeWithSession = (sessionId) => executeAgent({
|
|
54411
54470
|
model,
|
|
54412
54471
|
prompt,
|
|
54413
54472
|
systemPrompt,
|
|
@@ -54420,7 +54479,7 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
54420
54479
|
// CLI models: bypass permission prompts for headless operation
|
|
54421
54480
|
permissionMode: isCli ? "bypassPermissions" : void 0,
|
|
54422
54481
|
// CLI models: pass resumeSessionId for native --resume
|
|
54423
|
-
resumeSessionId,
|
|
54482
|
+
resumeSessionId: sessionId,
|
|
54424
54483
|
// API models: pass conversation history + persistence callback
|
|
54425
54484
|
previousMessages: isCli ? void 0 : previousMessages,
|
|
54426
54485
|
onSessionUpdate: isCli ? void 0 : (msgs) => {
|
|
@@ -54429,6 +54488,16 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
54429
54488
|
},
|
|
54430
54489
|
...apiToolOpts
|
|
54431
54490
|
});
|
|
54491
|
+
let result = await executeWithSession(resumeSessionId);
|
|
54492
|
+
if (isCli && result.exitCode !== 0) {
|
|
54493
|
+
const failure = result.output?.trim() || "";
|
|
54494
|
+
if (isCliContextOverflowError(failure)) {
|
|
54495
|
+
deleteAgentSession(db3, worker.id);
|
|
54496
|
+
logBuffer2.addSynthetic("system", "Session overflow detected \u2014 retrying this cycle with a fresh session");
|
|
54497
|
+
logBuffer2.flush();
|
|
54498
|
+
result = await executeWithSession(void 0);
|
|
54499
|
+
}
|
|
54500
|
+
}
|
|
54432
54501
|
const rateLimitInfo = checkRateLimit(result);
|
|
54433
54502
|
if (rateLimitInfo) {
|
|
54434
54503
|
throw new RateLimitError(rateLimitInfo);
|
|
@@ -54448,6 +54517,13 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
|
|
|
54448
54517
|
worker.id
|
|
54449
54518
|
);
|
|
54450
54519
|
updateAgentState(db3, worker.id, "idle");
|
|
54520
|
+
if (isCli) {
|
|
54521
|
+
if (isCliContextOverflowError(errorDetail)) {
|
|
54522
|
+
deleteAgentSession(db3, worker.id);
|
|
54523
|
+
logBuffer2.addSynthetic("system", "Session reset due to context overflow \u2014 next cycle will start fresh");
|
|
54524
|
+
logBuffer2.flush();
|
|
54525
|
+
}
|
|
54526
|
+
}
|
|
54451
54527
|
return result.output;
|
|
54452
54528
|
}
|
|
54453
54529
|
if (isCli && result.sessionId) {
|
|
@@ -54527,7 +54603,7 @@ var require_package = __commonJS({
|
|
|
54527
54603
|
"package.json"(exports2, module2) {
|
|
54528
54604
|
module2.exports = {
|
|
54529
54605
|
name: "quoroom",
|
|
54530
|
-
version: "0.1.
|
|
54606
|
+
version: "0.1.28",
|
|
54531
54607
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
54532
54608
|
main: "./out/mcp/server.js",
|
|
54533
54609
|
bin: {
|
|
@@ -55782,6 +55858,12 @@ function isRateLimitFailure(message) {
|
|
|
55782
55858
|
const lower = message.toLowerCase();
|
|
55783
55859
|
return lower.includes("rate limit") || lower.includes("limit reached") || lower.includes("too many requests") || lower.includes("insufficient_quota") || lower.includes("quota") || lower.includes("429") || lower.includes("limit") && lower.includes("reset");
|
|
55784
55860
|
}
|
|
55861
|
+
function isTransientFailure(message, timedOut) {
|
|
55862
|
+
if (timedOut) return true;
|
|
55863
|
+
if (isRateLimitFailure(message)) return true;
|
|
55864
|
+
const lower = message.toLowerCase();
|
|
55865
|
+
return lower.includes("timed out") || lower.includes("timeout") || lower.includes("temporarily unavailable") || lower.includes("service unavailable") || lower.includes("connection reset") || lower.includes("socket hang up") || lower.includes("econnreset") || lower.includes("etimedout") || lower.includes("eai_again");
|
|
55866
|
+
}
|
|
55785
55867
|
async function executeClerkWithFallback(options) {
|
|
55786
55868
|
const preferred = options.preferredModel?.trim() || DEFAULT_CLERK_MODEL;
|
|
55787
55869
|
const candidates = buildExecutionCandidates(options.db, preferred);
|
|
@@ -55820,7 +55902,7 @@ async function executeClerkWithFallback(options) {
|
|
|
55820
55902
|
const error2 = deriveExecutionFailure(result, candidate.model);
|
|
55821
55903
|
attempts.push({ model: candidate.model, error: error2 });
|
|
55822
55904
|
const hasMoreCandidates = i < candidates.length - 1;
|
|
55823
|
-
if (!hasMoreCandidates || !
|
|
55905
|
+
if (!hasMoreCandidates || !isTransientFailure(error2, result.timedOut)) {
|
|
55824
55906
|
return {
|
|
55825
55907
|
ok: false,
|
|
55826
55908
|
model: candidate.model,
|
|
@@ -57876,6 +57958,20 @@ function parseCursor(raw) {
|
|
|
57876
57958
|
if (!Number.isFinite(parsed) || parsed < 0) return 0;
|
|
57877
57959
|
return parsed;
|
|
57878
57960
|
}
|
|
57961
|
+
function parseIsoMs3(raw) {
|
|
57962
|
+
const value = String(raw ?? "").trim();
|
|
57963
|
+
if (!value) return null;
|
|
57964
|
+
const parsed = Date.parse(value);
|
|
57965
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
57966
|
+
}
|
|
57967
|
+
function resolveIntervalMs(db3, key, fallbackMs, minMs, maxMs) {
|
|
57968
|
+
const raw = (getSetting(db3, key) ?? "").trim();
|
|
57969
|
+
if (!raw) return fallbackMs;
|
|
57970
|
+
const minutes = Number.parseInt(raw, 10);
|
|
57971
|
+
if (!Number.isFinite(minutes) || minutes < 0) return fallbackMs;
|
|
57972
|
+
if (minutes === 0) return 0;
|
|
57973
|
+
return Math.min(maxMs, Math.max(minMs, minutes * 6e4));
|
|
57974
|
+
}
|
|
57879
57975
|
function getKeeperUserNumber2(db3) {
|
|
57880
57976
|
const raw = (getSetting(db3, "keeper_user_number") ?? "").trim();
|
|
57881
57977
|
if (!/^\d{5,6}$/.test(raw)) return null;
|
|
@@ -58022,6 +58118,20 @@ function buildKeeperDigest(db3, input, styleCursor) {
|
|
|
58022
58118
|
);
|
|
58023
58119
|
return lines.join("\n");
|
|
58024
58120
|
}
|
|
58121
|
+
function getPendingDigestCounts(input) {
|
|
58122
|
+
const escalations = input.escalations.length;
|
|
58123
|
+
const decisions = input.decisions.length;
|
|
58124
|
+
const roomMessages = input.roomMessages.length;
|
|
58125
|
+
return {
|
|
58126
|
+
escalations,
|
|
58127
|
+
decisions,
|
|
58128
|
+
roomMessages,
|
|
58129
|
+
total: escalations + decisions + roomMessages
|
|
58130
|
+
};
|
|
58131
|
+
}
|
|
58132
|
+
function isUrgentDigest(counts) {
|
|
58133
|
+
return counts.escalations >= URGENT_ESCALATION_THRESHOLD || counts.decisions >= URGENT_DECISION_THRESHOLD || counts.total >= URGENT_TOTAL_THRESHOLD;
|
|
58134
|
+
}
|
|
58025
58135
|
async function relayPendingKeeperRequests(db3) {
|
|
58026
58136
|
if (isCloudDeployment()) return;
|
|
58027
58137
|
const channels = getClerkPreferredNotifyChannels(db3);
|
|
@@ -58054,6 +58164,32 @@ async function relayPendingKeeperRequests(db3) {
|
|
|
58054
58164
|
roomMessages: pendingRoomMessages
|
|
58055
58165
|
}, digestStyleCursor);
|
|
58056
58166
|
if (!content) return;
|
|
58167
|
+
const pendingCounts = getPendingDigestCounts({
|
|
58168
|
+
escalations: pendingEscalations,
|
|
58169
|
+
decisions: pendingDecisions,
|
|
58170
|
+
roomMessages: pendingRoomMessages
|
|
58171
|
+
});
|
|
58172
|
+
const minIntervalMs = resolveIntervalMs(
|
|
58173
|
+
db3,
|
|
58174
|
+
CLERK_NOTIFY_MIN_INTERVAL_MINUTES_KEY,
|
|
58175
|
+
DEFAULT_NOTIFY_MIN_INTERVAL_MS,
|
|
58176
|
+
MIN_NOTIFY_INTERVAL_MS,
|
|
58177
|
+
MAX_NOTIFY_INTERVAL_MS
|
|
58178
|
+
);
|
|
58179
|
+
const urgentIntervalMs = resolveIntervalMs(
|
|
58180
|
+
db3,
|
|
58181
|
+
CLERK_NOTIFY_URGENT_MIN_INTERVAL_MINUTES_KEY,
|
|
58182
|
+
DEFAULT_NOTIFY_URGENT_MIN_INTERVAL_MS,
|
|
58183
|
+
MIN_URGENT_INTERVAL_MS,
|
|
58184
|
+
MAX_URGENT_INTERVAL_MS
|
|
58185
|
+
);
|
|
58186
|
+
const lastSentMs = parseIsoMs3(getSetting(db3, CLERK_NOTIFY_LAST_SENT_AT_KEY));
|
|
58187
|
+
if (lastSentMs != null) {
|
|
58188
|
+
const elapsedMs = Date.now() - lastSentMs;
|
|
58189
|
+
const regularAllowed = minIntervalMs <= 0 || elapsedMs >= minIntervalMs;
|
|
58190
|
+
const urgentAllowed = isUrgentDigest(pendingCounts) && (urgentIntervalMs <= 0 || elapsedMs >= urgentIntervalMs);
|
|
58191
|
+
if (!regularAllowed && !urgentAllowed) return;
|
|
58192
|
+
}
|
|
58057
58193
|
const sent = await sendClerkAlert(db3, { content });
|
|
58058
58194
|
if (!sent) return;
|
|
58059
58195
|
const nextEscalationCursor = pendingEscalations.reduce((max, item) => Math.max(max, item.escalation?.id ?? 0), escalationCursor);
|
|
@@ -58063,6 +58199,7 @@ async function relayPendingKeeperRequests(db3) {
|
|
|
58063
58199
|
setSetting(db3, CLERK_NOTIFY_DECISION_CURSOR_KEY, String(nextDecisionCursor));
|
|
58064
58200
|
setSetting(db3, CLERK_NOTIFY_ROOM_MESSAGE_CURSOR_KEY, String(nextRoomMessageCursor));
|
|
58065
58201
|
setSetting(db3, CLERK_NOTIFY_DIGEST_STYLE_CURSOR_KEY, String(digestStyleCursor + 1));
|
|
58202
|
+
setSetting(db3, CLERK_NOTIFY_LAST_SENT_AT_KEY, (/* @__PURE__ */ new Date()).toISOString());
|
|
58066
58203
|
const byRoom = /* @__PURE__ */ new Map();
|
|
58067
58204
|
for (const item of pendingEscalations) {
|
|
58068
58205
|
if (!item.escalation) continue;
|
|
@@ -58096,7 +58233,7 @@ async function relayPendingKeeperRequests(db3) {
|
|
|
58096
58233
|
);
|
|
58097
58234
|
}
|
|
58098
58235
|
}
|
|
58099
|
-
var CLERK_NOTIFY_EMAIL_KEY, CLERK_NOTIFY_TELEGRAM_KEY, CLERK_NOTIFY_ESCALATION_CURSOR_KEY, CLERK_NOTIFY_DECISION_CURSOR_KEY, CLERK_NOTIFY_ROOM_MESSAGE_CURSOR_KEY, CLERK_NOTIFY_DIGEST_STYLE_CURSOR_KEY, DIGEST_OPENERS, ESCALATION_HEADERS, DECISION_HEADERS, ROOM_MESSAGE_HEADERS, DIGEST_CLOSERS, DIGEST_SECTION_LIMIT;
|
|
58236
|
+
var CLERK_NOTIFY_EMAIL_KEY, CLERK_NOTIFY_TELEGRAM_KEY, CLERK_NOTIFY_MIN_INTERVAL_MINUTES_KEY, CLERK_NOTIFY_URGENT_MIN_INTERVAL_MINUTES_KEY, CLERK_NOTIFY_ESCALATION_CURSOR_KEY, CLERK_NOTIFY_DECISION_CURSOR_KEY, CLERK_NOTIFY_ROOM_MESSAGE_CURSOR_KEY, CLERK_NOTIFY_DIGEST_STYLE_CURSOR_KEY, CLERK_NOTIFY_LAST_SENT_AT_KEY, DEFAULT_NOTIFY_MIN_INTERVAL_MS, DEFAULT_NOTIFY_URGENT_MIN_INTERVAL_MS, MIN_NOTIFY_INTERVAL_MS, MAX_NOTIFY_INTERVAL_MS, MIN_URGENT_INTERVAL_MS, MAX_URGENT_INTERVAL_MS, URGENT_ESCALATION_THRESHOLD, URGENT_DECISION_THRESHOLD, URGENT_TOTAL_THRESHOLD, DIGEST_OPENERS, ESCALATION_HEADERS, DECISION_HEADERS, ROOM_MESSAGE_HEADERS, DIGEST_CLOSERS, DIGEST_SECTION_LIMIT;
|
|
58100
58237
|
var init_clerk_notifications = __esm({
|
|
58101
58238
|
"src/server/clerk-notifications.ts"() {
|
|
58102
58239
|
"use strict";
|
|
@@ -58106,10 +58243,22 @@ var init_clerk_notifications = __esm({
|
|
|
58106
58243
|
init_clerk_message_events();
|
|
58107
58244
|
CLERK_NOTIFY_EMAIL_KEY = "clerk_notify_email";
|
|
58108
58245
|
CLERK_NOTIFY_TELEGRAM_KEY = "clerk_notify_telegram";
|
|
58246
|
+
CLERK_NOTIFY_MIN_INTERVAL_MINUTES_KEY = "clerk_notify_min_interval_minutes";
|
|
58247
|
+
CLERK_NOTIFY_URGENT_MIN_INTERVAL_MINUTES_KEY = "clerk_notify_urgent_min_interval_minutes";
|
|
58109
58248
|
CLERK_NOTIFY_ESCALATION_CURSOR_KEY = "clerk_notify_escalation_cursor";
|
|
58110
58249
|
CLERK_NOTIFY_DECISION_CURSOR_KEY = "clerk_notify_decision_cursor";
|
|
58111
58250
|
CLERK_NOTIFY_ROOM_MESSAGE_CURSOR_KEY = "clerk_notify_room_message_cursor";
|
|
58112
58251
|
CLERK_NOTIFY_DIGEST_STYLE_CURSOR_KEY = "clerk_notify_digest_style_cursor";
|
|
58252
|
+
CLERK_NOTIFY_LAST_SENT_AT_KEY = "clerk_notify_last_sent_at";
|
|
58253
|
+
DEFAULT_NOTIFY_MIN_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
58254
|
+
DEFAULT_NOTIFY_URGENT_MIN_INTERVAL_MS = 60 * 60 * 1e3;
|
|
58255
|
+
MIN_NOTIFY_INTERVAL_MS = 30 * 60 * 1e3;
|
|
58256
|
+
MAX_NOTIFY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
58257
|
+
MIN_URGENT_INTERVAL_MS = 10 * 60 * 1e3;
|
|
58258
|
+
MAX_URGENT_INTERVAL_MS = 12 * 60 * 60 * 1e3;
|
|
58259
|
+
URGENT_ESCALATION_THRESHOLD = 6;
|
|
58260
|
+
URGENT_DECISION_THRESHOLD = 4;
|
|
58261
|
+
URGENT_TOTAL_THRESHOLD = 12;
|
|
58113
58262
|
DIGEST_OPENERS = [
|
|
58114
58263
|
(count) => `I need your call on ${count} item${count === 1 ? "" : "s"}.`,
|
|
58115
58264
|
(count) => `${count} decision point${count === 1 ? "" : "s"} need${count === 1 ? "s" : ""} your direction right now.`,
|
|
@@ -60255,7 +60404,7 @@ function semverGt(a, b) {
|
|
|
60255
60404
|
}
|
|
60256
60405
|
function getCurrentVersion() {
|
|
60257
60406
|
try {
|
|
60258
|
-
return true ? "0.1.
|
|
60407
|
+
return true ? "0.1.28" : null.version;
|
|
60259
60408
|
} catch {
|
|
60260
60409
|
return "0.0.0";
|
|
60261
60410
|
}
|
|
@@ -60440,7 +60589,7 @@ var init_updateChecker = __esm({
|
|
|
60440
60589
|
function getVersion3() {
|
|
60441
60590
|
if (cachedVersion) return cachedVersion;
|
|
60442
60591
|
try {
|
|
60443
|
-
cachedVersion = true ? "0.1.
|
|
60592
|
+
cachedVersion = true ? "0.1.28" : null.version;
|
|
60444
60593
|
} catch {
|
|
60445
60594
|
cachedVersion = "unknown";
|
|
60446
60595
|
}
|
|
@@ -66869,7 +67018,7 @@ __export(update_exports, {
|
|
|
66869
67018
|
});
|
|
66870
67019
|
function getCurrentVersion2() {
|
|
66871
67020
|
try {
|
|
66872
|
-
return true ? "0.1.
|
|
67021
|
+
return true ? "0.1.28" : null.version;
|
|
66873
67022
|
} catch {
|
|
66874
67023
|
return "0.0.0";
|
|
66875
67024
|
}
|
package/out/mcp/server.js
CHANGED
|
@@ -7466,7 +7466,6 @@ Rules:
|
|
|
7466
7466
|
DEFAULT_ROOM_CONFIG = {
|
|
7467
7467
|
threshold: "majority",
|
|
7468
7468
|
timeoutMinutes: 60,
|
|
7469
|
-
keeperWeight: "dynamic",
|
|
7470
7469
|
tieBreaker: "queen",
|
|
7471
7470
|
autoApprove: ["low_impact"],
|
|
7472
7471
|
minCycleGapMs: 1e3,
|
|
@@ -8611,10 +8610,10 @@ function getRoomActivity(db2, roomId, limit = 50, eventTypes) {
|
|
|
8611
8610
|
const safeLimit = clampLimit(limit, 50, 500);
|
|
8612
8611
|
if (eventTypes && eventTypes.length > 0) {
|
|
8613
8612
|
const placeholders = eventTypes.map(() => "?").join(", ");
|
|
8614
|
-
const rows2 = db2.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC LIMIT ?`).all(roomId, ...eventTypes, safeLimit);
|
|
8613
|
+
const rows2 = db2.prepare(`SELECT * FROM room_activity WHERE room_id = ? AND event_type IN (${placeholders}) ORDER BY created_at DESC, id DESC LIMIT ?`).all(roomId, ...eventTypes, safeLimit);
|
|
8615
8614
|
return rows2.map(mapRoomActivityRow);
|
|
8616
8615
|
}
|
|
8617
|
-
const rows = db2.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC LIMIT ?").all(roomId, safeLimit);
|
|
8616
|
+
const rows = db2.prepare("SELECT * FROM room_activity WHERE room_id = ? ORDER BY created_at DESC, id DESC LIMIT ?").all(roomId, safeLimit);
|
|
8618
8617
|
return rows.map(mapRoomActivityRow);
|
|
8619
8618
|
}
|
|
8620
8619
|
function mapDecisionRow(row) {
|
|
@@ -8944,7 +8943,24 @@ function mapEscalationRow(row) {
|
|
|
8944
8943
|
}
|
|
8945
8944
|
function createEscalation(db2, roomId, fromAgentId, question, toAgentId) {
|
|
8946
8945
|
const result = db2.prepare("INSERT INTO escalations (room_id, from_agent_id, to_agent_id, question) VALUES (?, ?, ?, ?)").run(roomId, fromAgentId, toAgentId ?? null, question);
|
|
8947
|
-
|
|
8946
|
+
const escalation = getEscalation(db2, result.lastInsertRowid);
|
|
8947
|
+
const trimmedQuestion = question.trim();
|
|
8948
|
+
const detail = trimmedQuestion.length > 1e3 ? `${trimmedQuestion.slice(0, 1e3)}\u2026` : trimmedQuestion;
|
|
8949
|
+
let summary = "Message created";
|
|
8950
|
+
if (toAgentId == null) {
|
|
8951
|
+
summary = fromAgentId != null ? `Worker #${fromAgentId} sent message to keeper` : "Message sent to keeper";
|
|
8952
|
+
} else {
|
|
8953
|
+
summary = fromAgentId != null ? `Worker #${fromAgentId} sent message to worker #${toAgentId}` : `Keeper sent message to worker #${toAgentId}`;
|
|
8954
|
+
}
|
|
8955
|
+
logRoomActivity(
|
|
8956
|
+
db2,
|
|
8957
|
+
roomId,
|
|
8958
|
+
fromAgentId != null ? "worker" : "system",
|
|
8959
|
+
summary,
|
|
8960
|
+
detail || void 0,
|
|
8961
|
+
fromAgentId ?? void 0
|
|
8962
|
+
);
|
|
8963
|
+
return escalation;
|
|
8948
8964
|
}
|
|
8949
8965
|
function getEscalation(db2, id) {
|
|
8950
8966
|
const row = db2.prepare("SELECT * FROM escalations WHERE id = ?").get(id);
|
|
@@ -8967,7 +8983,24 @@ function listEscalations(db2, roomId, status) {
|
|
|
8967
8983
|
return rows.map(mapEscalationRow);
|
|
8968
8984
|
}
|
|
8969
8985
|
function resolveEscalation(db2, id, answer) {
|
|
8986
|
+
const escalation = getEscalation(db2, id);
|
|
8970
8987
|
db2.prepare("UPDATE escalations SET answer = ?, status = 'resolved', resolved_at = datetime('now','localtime') WHERE id = ?").run(answer, id);
|
|
8988
|
+
if (!escalation) return;
|
|
8989
|
+
const trimmedAnswer = answer.trim();
|
|
8990
|
+
const detail = trimmedAnswer.length > 1e3 ? `${trimmedAnswer.slice(0, 1e3)}\u2026` : trimmedAnswer;
|
|
8991
|
+
let summary = "Message resolved";
|
|
8992
|
+
if (escalation.toAgentId == null && escalation.fromAgentId != null) {
|
|
8993
|
+
summary = `Keeper replied to worker #${escalation.fromAgentId}`;
|
|
8994
|
+
} else if (escalation.toAgentId != null) {
|
|
8995
|
+
summary = `Message resolved for worker #${escalation.toAgentId}`;
|
|
8996
|
+
}
|
|
8997
|
+
logRoomActivity(
|
|
8998
|
+
db2,
|
|
8999
|
+
escalation.roomId,
|
|
9000
|
+
"system",
|
|
9001
|
+
summary,
|
|
9002
|
+
detail || void 0
|
|
9003
|
+
);
|
|
8971
9004
|
}
|
|
8972
9005
|
function getRecentKeeperAnswers(db2, roomId, fromAgentId, limit = 5) {
|
|
8973
9006
|
const rows = db2.prepare(
|
|
@@ -9493,6 +9526,9 @@ function mapCycleLogRow(row) {
|
|
|
9493
9526
|
};
|
|
9494
9527
|
}
|
|
9495
9528
|
function createWorkerCycle(db2, workerId, roomId, model) {
|
|
9529
|
+
db2.prepare(
|
|
9530
|
+
"UPDATE worker_cycles SET status = 'failed', error_message = 'Superseded by newer cycle', finished_at = datetime('now','localtime') WHERE worker_id = ? AND status = 'running'"
|
|
9531
|
+
).run(workerId);
|
|
9496
9532
|
const result = db2.prepare(
|
|
9497
9533
|
"INSERT INTO worker_cycles (worker_id, room_id, model) VALUES (?, ?, ?)"
|
|
9498
9534
|
).run(workerId, roomId, model);
|
|
@@ -9514,7 +9550,7 @@ function completeWorkerCycle(db2, cycleId, errorMessage, usage) {
|
|
|
9514
9550
|
function listRoomCycles(db2, roomId, limit = 20) {
|
|
9515
9551
|
const safeLimit = clampLimit(limit, 20, 200);
|
|
9516
9552
|
const rows = db2.prepare(
|
|
9517
|
-
"SELECT * FROM worker_cycles WHERE room_id = ? ORDER BY started_at DESC LIMIT ?"
|
|
9553
|
+
"SELECT * FROM worker_cycles WHERE room_id = ? ORDER BY started_at DESC, id DESC LIMIT ?"
|
|
9518
9554
|
).all(roomId, safeLimit);
|
|
9519
9555
|
return rows.map(mapWorkerCycleRow);
|
|
9520
9556
|
}
|
|
@@ -46565,7 +46601,6 @@ function registerRoomTools(server) {
|
|
|
46565
46601
|
maxConcurrentTasks: external_exports.number().int().min(1).max(10).optional().describe("Maximum parallel tasks (1\u201310)"),
|
|
46566
46602
|
threshold: external_exports.enum(["majority", "supermajority", "unanimous"]).optional().describe("Voting threshold for quorum decisions"),
|
|
46567
46603
|
autoApprove: external_exports.array(external_exports.string()).optional().describe('Decision types to auto-approve without voting (e.g. ["low_impact"])'),
|
|
46568
|
-
keeperWeight: external_exports.enum(["dynamic", "equal"]).optional().describe("Keeper vote weighting: dynamic (51% in small rooms) or equal"),
|
|
46569
46604
|
tieBreaker: external_exports.enum(["queen", "none"]).optional().describe("Tie-breaking strategy: queen vote decides, or reject on tie"),
|
|
46570
46605
|
minVoters: external_exports.number().int().min(0).max(100).optional().describe("Minimum non-abstain votes required for a decision to pass (0 = no minimum)"),
|
|
46571
46606
|
sealedBallot: external_exports.boolean().optional().describe("When true, individual votes are hidden until the decision resolves"),
|
|
@@ -46580,7 +46615,6 @@ function registerRoomTools(server) {
|
|
|
46580
46615
|
maxConcurrentTasks,
|
|
46581
46616
|
threshold,
|
|
46582
46617
|
autoApprove,
|
|
46583
|
-
keeperWeight,
|
|
46584
46618
|
tieBreaker,
|
|
46585
46619
|
minVoters,
|
|
46586
46620
|
sealedBallot,
|
|
@@ -46615,10 +46649,6 @@ function registerRoomTools(server) {
|
|
|
46615
46649
|
configUpdates.autoApprove = autoApprove;
|
|
46616
46650
|
changes.push(`autoApprove: [${room.config.autoApprove.join(",")}] \u2192 [${autoApprove.join(",")}]`);
|
|
46617
46651
|
}
|
|
46618
|
-
if (keeperWeight !== void 0) {
|
|
46619
|
-
configUpdates.keeperWeight = keeperWeight;
|
|
46620
|
-
changes.push(`keeperWeight: ${room.config.keeperWeight} \u2192 ${keeperWeight}`);
|
|
46621
|
-
}
|
|
46622
46652
|
if (tieBreaker !== void 0) {
|
|
46623
46653
|
configUpdates.tieBreaker = tieBreaker;
|
|
46624
46654
|
changes.push(`tieBreaker: ${room.config.tieBreaker} \u2192 ${tieBreaker}`);
|
|
@@ -46751,8 +46781,6 @@ function tally(db2, decisionId) {
|
|
|
46751
46781
|
return "rejected";
|
|
46752
46782
|
}
|
|
46753
46783
|
}
|
|
46754
|
-
const keeperWeightMode = room?.config.keeperWeight ?? "dynamic";
|
|
46755
|
-
const useWeighted = keeperWeightMode === "dynamic" && voters.length <= 1;
|
|
46756
46784
|
const queenWorkerId = room?.queenWorkerId ?? null;
|
|
46757
46785
|
const tieBreakerMode = room?.config.tieBreaker ?? "queen";
|
|
46758
46786
|
let yesWeight = 0;
|
|
@@ -46765,9 +46793,8 @@ function tally(db2, decisionId) {
|
|
|
46765
46793
|
}
|
|
46766
46794
|
const kv = decision.keeperVote;
|
|
46767
46795
|
if (kv && kv !== "abstain") {
|
|
46768
|
-
|
|
46769
|
-
if (kv === "
|
|
46770
|
-
else if (kv === "no") noWeight += keeperWeight;
|
|
46796
|
+
if (kv === "yes") yesWeight += 1;
|
|
46797
|
+
else if (kv === "no") noWeight += 1;
|
|
46771
46798
|
} else if (kv === "abstain") {
|
|
46772
46799
|
abstainCount++;
|
|
46773
46800
|
}
|
|
@@ -46794,9 +46821,7 @@ function tally(db2, decisionId) {
|
|
|
46794
46821
|
}
|
|
46795
46822
|
}
|
|
46796
46823
|
}
|
|
46797
|
-
const
|
|
46798
|
-
const noDisplay = useWeighted ? noWeight.toFixed(2) : String(noWeight);
|
|
46799
|
-
const result = `Yes: ${yesDisplay}, No: ${noDisplay}, Abstain: ${abstainCount}` + (useWeighted ? " (weighted)" : "");
|
|
46824
|
+
const result = `Yes: ${yesWeight}, No: ${noWeight}, Abstain: ${abstainCount}`;
|
|
46800
46825
|
resolveDecision(db2, decisionId, status, result);
|
|
46801
46826
|
logRoomActivity(
|
|
46802
46827
|
db2,
|
|
@@ -49195,7 +49220,7 @@ init_db();
|
|
|
49195
49220
|
async function main() {
|
|
49196
49221
|
const server = new McpServer({
|
|
49197
49222
|
name: "quoroom",
|
|
49198
|
-
version: true ? "0.1.
|
|
49223
|
+
version: true ? "0.1.28" : "0.0.0"
|
|
49199
49224
|
});
|
|
49200
49225
|
registerMemoryTools(server);
|
|
49201
49226
|
registerSchedulerTools(server);
|