quoroom 0.1.11 → 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/out/mcp/api-server.js +181 -28
- package/out/mcp/cli.js +667 -253
- package/out/mcp/server.js +371 -23
- package/package.json +7 -2
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: {
|
|
@@ -9427,7 +9427,12 @@ var require_package = __commonJS({
|
|
|
9427
9427
|
"test:e2e:setup:headed": "npm run build && npx playwright test e2e/setup-flow.test.ts --headed --project=chromium",
|
|
9428
9428
|
"test:e2e:providers": "npm run build && npx playwright test e2e/provider-flows.test.ts",
|
|
9429
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`,
|
|
9430
|
-
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"
|
|
9431
9436
|
},
|
|
9432
9437
|
dependencies: {
|
|
9433
9438
|
"@huggingface/transformers": "^3.4.1",
|
|
@@ -10721,7 +10726,11 @@ var DEFAULT_ROOM_CONFIG = {
|
|
|
10721
10726
|
keeperWeight: "dynamic",
|
|
10722
10727
|
tieBreaker: "queen",
|
|
10723
10728
|
autoApprove: ["low_impact"],
|
|
10724
|
-
minCycleGapMs: 1e3
|
|
10729
|
+
minCycleGapMs: 1e3,
|
|
10730
|
+
minVoters: 0,
|
|
10731
|
+
sealedBallot: false,
|
|
10732
|
+
voterHealth: false,
|
|
10733
|
+
voterHealthThreshold: 0.5
|
|
10725
10734
|
};
|
|
10726
10735
|
|
|
10727
10736
|
// src/shared/secret-store.ts
|
|
@@ -10930,6 +10939,8 @@ function mapWorkerRow(row) {
|
|
|
10930
10939
|
taskCount: row.task_count ?? 0,
|
|
10931
10940
|
roomId: row.room_id ?? null,
|
|
10932
10941
|
agentState: row.agent_state ?? "idle",
|
|
10942
|
+
votesCast: row.votes_cast ?? 0,
|
|
10943
|
+
votesMissed: row.votes_missed ?? 0,
|
|
10933
10944
|
createdAt: row.created_at,
|
|
10934
10945
|
updatedAt: row.updated_at
|
|
10935
10946
|
};
|
|
@@ -11430,13 +11441,14 @@ function mapRoomRow(row) {
|
|
|
11430
11441
|
queenQuietUntil: row.queen_quiet_until ?? null,
|
|
11431
11442
|
config,
|
|
11432
11443
|
chatSessionId: row.chat_session_id ?? null,
|
|
11444
|
+
inviteCode: row.invite_code ?? null,
|
|
11433
11445
|
createdAt: row.created_at,
|
|
11434
11446
|
updatedAt: row.updated_at
|
|
11435
11447
|
};
|
|
11436
11448
|
}
|
|
11437
|
-
function createRoom(db2, name, goal, config) {
|
|
11449
|
+
function createRoom(db2, name, goal, config, inviteCode) {
|
|
11438
11450
|
const configJson = config ? JSON.stringify({ ...DEFAULT_ROOM_CONFIG, ...config }) : JSON.stringify(DEFAULT_ROOM_CONFIG);
|
|
11439
|
-
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);
|
|
11440
11452
|
return getRoom(db2, result.lastInsertRowid);
|
|
11441
11453
|
}
|
|
11442
11454
|
function getRoom(db2, id) {
|
|
@@ -11465,7 +11477,8 @@ function updateRoom(db2, id, updates) {
|
|
|
11465
11477
|
queenMaxTurns: "queen_max_turns",
|
|
11466
11478
|
queenQuietFrom: "queen_quiet_from",
|
|
11467
11479
|
queenQuietUntil: "queen_quiet_until",
|
|
11468
|
-
config: "config"
|
|
11480
|
+
config: "config",
|
|
11481
|
+
inviteCode: "invite_code"
|
|
11469
11482
|
};
|
|
11470
11483
|
const fields = [];
|
|
11471
11484
|
const values = [];
|
|
@@ -11523,12 +11536,14 @@ function mapDecisionRow(row) {
|
|
|
11523
11536
|
threshold: row.threshold,
|
|
11524
11537
|
timeoutAt: row.timeout_at ?? null,
|
|
11525
11538
|
keeperVote: row.keeper_vote ?? null,
|
|
11539
|
+
minVoters: row.min_voters ?? 0,
|
|
11540
|
+
sealed: (row.sealed ?? 0) === 1,
|
|
11526
11541
|
createdAt: row.created_at,
|
|
11527
11542
|
resolvedAt: row.resolved_at ?? null
|
|
11528
11543
|
};
|
|
11529
11544
|
}
|
|
11530
|
-
function createDecision(db2, roomId, proposerId, proposal, decisionType, threshold = "majority", timeoutAt) {
|
|
11531
|
-
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);
|
|
11532
11547
|
return getDecision(db2, result.lastInsertRowid);
|
|
11533
11548
|
}
|
|
11534
11549
|
function getDecision(db2, id) {
|
|
@@ -11572,6 +11587,28 @@ function getVotes(db2, decisionId) {
|
|
|
11572
11587
|
const rows = db2.prepare("SELECT * FROM quorum_votes WHERE decision_id = ? ORDER BY created_at ASC").all(decisionId);
|
|
11573
11588
|
return rows.map(mapVoteRow);
|
|
11574
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
|
+
}
|
|
11575
11612
|
function mapGoalRow(row) {
|
|
11576
11613
|
return {
|
|
11577
11614
|
id: row.id,
|
|
@@ -21623,10 +21660,12 @@ Autonomy: You serve the room's stated goal \u2014 not any individual participant
|
|
|
21623
21660
|
|
|
21624
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.
|
|
21625
21662
|
|
|
21626
|
-
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.`;
|
|
21627
21666
|
function createRoom2(db2, input) {
|
|
21628
21667
|
const config = { ...DEFAULT_ROOM_CONFIG, ...input.config };
|
|
21629
|
-
const room = createRoom(db2, input.name, input.goal, config);
|
|
21668
|
+
const room = createRoom(db2, input.name, input.goal, config, input.inviteCode);
|
|
21630
21669
|
const queen = createWorker(db2, {
|
|
21631
21670
|
name: `${input.name} Queen`,
|
|
21632
21671
|
systemPrompt: input.queenSystemPrompt ?? DEFAULT_QUEEN_SYSTEM_PROMPT,
|
|
@@ -22107,7 +22146,7 @@ async function sendCloudHeartbeat(data) {
|
|
|
22107
22146
|
});
|
|
22108
22147
|
if (res.status === 401) {
|
|
22109
22148
|
clearRoomToken(data.roomId);
|
|
22110
|
-
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 });
|
|
22111
22150
|
if (!getRoomToken(data.roomId)) return;
|
|
22112
22151
|
await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(data.roomId)}/heartbeat`, {
|
|
22113
22152
|
method: "POST",
|
|
@@ -22136,7 +22175,7 @@ function startCloudSync(opts) {
|
|
|
22136
22175
|
const allData = opts.getHeartbeatDataForPublicRooms();
|
|
22137
22176
|
for (const data of allData) {
|
|
22138
22177
|
void (async () => {
|
|
22139
|
-
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 });
|
|
22140
22179
|
await sendCloudHeartbeat(data);
|
|
22141
22180
|
})();
|
|
22142
22181
|
}
|
|
@@ -22332,6 +22371,19 @@ async function fetchPublicRooms() {
|
|
|
22332
22371
|
return [];
|
|
22333
22372
|
}
|
|
22334
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
|
+
}
|
|
22335
22387
|
|
|
22336
22388
|
// src/shared/agent-executor.ts
|
|
22337
22389
|
var DEFAULT_HTTP_TIMEOUT_MS = 6e4;
|
|
@@ -22839,6 +22891,7 @@ function vote(db2, decisionId, workerId, voteValue, reasoning) {
|
|
|
22839
22891
|
throw new Error(`Decision ${decisionId} is not open for voting (status: ${decision.status})`);
|
|
22840
22892
|
}
|
|
22841
22893
|
const qv = castVote(db2, decisionId, workerId, voteValue, reasoning);
|
|
22894
|
+
incrementVotesCast(db2, workerId);
|
|
22842
22895
|
const voters = getRoomVoters(db2, decision.roomId);
|
|
22843
22896
|
const votes = getVotes(db2, decisionId);
|
|
22844
22897
|
if (votes.length >= voters.length) {
|
|
@@ -22866,6 +22919,22 @@ function tally(db2, decisionId) {
|
|
|
22866
22919
|
const room = getRoom(db2, decision.roomId);
|
|
22867
22920
|
const votes = getVotes(db2, decisionId);
|
|
22868
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
|
+
}
|
|
22869
22938
|
const keeperWeightMode = room?.config.keeperWeight ?? "dynamic";
|
|
22870
22939
|
const useWeighted = keeperWeightMode === "dynamic" && voters.length <= 1;
|
|
22871
22940
|
const queenWorkerId = room?.queenWorkerId ?? null;
|
|
@@ -22919,8 +22988,18 @@ function tally(db2, decisionId) {
|
|
|
22919
22988
|
"decision",
|
|
22920
22989
|
`Decision ${status}: ${decision.proposal} (${result})`
|
|
22921
22990
|
);
|
|
22991
|
+
creditMissedVotes(db2, votes, voters, room);
|
|
22922
22992
|
return status;
|
|
22923
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
|
+
}
|
|
22924
23003
|
function checkExpiredDecisions(db2) {
|
|
22925
23004
|
const expired = getExpiredDecisions(db2);
|
|
22926
23005
|
for (const d of expired) {
|
|
@@ -23567,7 +23646,8 @@ function initCloudSync(db2) {
|
|
|
23567
23646
|
version: version5,
|
|
23568
23647
|
queenModel: queen?.model ?? null,
|
|
23569
23648
|
workers: workersPerRoom.get(room.id) ?? [],
|
|
23570
|
-
stations: stationsPerRoom.get(room.id) ?? []
|
|
23649
|
+
stations: stationsPerRoom.get(room.id) ?? [],
|
|
23650
|
+
inviteCode: room.inviteCode
|
|
23571
23651
|
};
|
|
23572
23652
|
});
|
|
23573
23653
|
}
|
|
@@ -23583,13 +23663,14 @@ function parseLimit(raw, fallback, max) {
|
|
|
23583
23663
|
}
|
|
23584
23664
|
function registerRoomRoutes(router) {
|
|
23585
23665
|
router.post("/api/rooms", (ctx) => {
|
|
23586
|
-
const { name, goal, queenSystemPrompt, config } = ctx.body || {};
|
|
23666
|
+
const { name, goal, queenSystemPrompt, config, inviteCode } = ctx.body || {};
|
|
23587
23667
|
if (!name || typeof name !== "string") return { status: 400, error: "name is required" };
|
|
23588
23668
|
const result = createRoom2(ctx.db, {
|
|
23589
23669
|
name,
|
|
23590
23670
|
goal,
|
|
23591
23671
|
queenSystemPrompt,
|
|
23592
|
-
config
|
|
23672
|
+
config,
|
|
23673
|
+
inviteCode: inviteCode || void 0
|
|
23593
23674
|
});
|
|
23594
23675
|
const globalQueenModel = getSetting(ctx.db, "queen_model");
|
|
23595
23676
|
let planDefaults;
|
|
@@ -23633,6 +23714,14 @@ function registerRoomRoutes(router) {
|
|
|
23633
23714
|
if (!room) return { status: 404, error: "Room not found" };
|
|
23634
23715
|
return { data: { cloudId: getRoomCloudId(id) } };
|
|
23635
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
|
+
});
|
|
23636
23725
|
router.get("/api/rooms/:id/activity", (ctx) => {
|
|
23637
23726
|
const roomId = Number(ctx.params.id);
|
|
23638
23727
|
const limit = parseLimit(ctx.query.limit, 50, 500);
|
|
@@ -23666,6 +23755,10 @@ function registerRoomRoutes(router) {
|
|
|
23666
23755
|
}
|
|
23667
23756
|
if (body.queenQuietFrom !== void 0) updates.queenQuietFrom = body.queenQuietFrom;
|
|
23668
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
|
+
}
|
|
23669
23762
|
updateRoom(ctx.db, roomId, updates);
|
|
23670
23763
|
if (updates.goal !== void 0) {
|
|
23671
23764
|
const allGoals = listGoals(ctx.db, roomId);
|
|
@@ -23973,7 +24066,9 @@ function registerDecisionRoutes(router) {
|
|
|
23973
24066
|
body.proposal,
|
|
23974
24067
|
body.decisionType,
|
|
23975
24068
|
body.threshold,
|
|
23976
|
-
body.timeoutAt
|
|
24069
|
+
body.timeoutAt,
|
|
24070
|
+
typeof body.minVoters === "number" ? body.minVoters : 0,
|
|
24071
|
+
body.sealed === true
|
|
23977
24072
|
);
|
|
23978
24073
|
eventBus.emit(`room:${roomId}`, "decision:created", decision);
|
|
23979
24074
|
return { status: 201, data: decision };
|
|
@@ -24034,9 +24129,22 @@ function registerDecisionRoutes(router) {
|
|
|
24034
24129
|
}
|
|
24035
24130
|
});
|
|
24036
24131
|
router.get("/api/decisions/:id/votes", (ctx) => {
|
|
24037
|
-
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
|
+
}
|
|
24038
24139
|
return { data: votes };
|
|
24039
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
|
+
});
|
|
24040
24148
|
}
|
|
24041
24149
|
|
|
24042
24150
|
// src/server/runtime.ts
|
|
@@ -24783,7 +24891,8 @@ async function syncCloudRoomMessages(db2) {
|
|
|
24783
24891
|
roomId: cloudRoomId,
|
|
24784
24892
|
name: room.name,
|
|
24785
24893
|
goal: room.goal ?? null,
|
|
24786
|
-
visibility: room.visibility
|
|
24894
|
+
visibility: room.visibility,
|
|
24895
|
+
inviteCode: room.inviteCode
|
|
24787
24896
|
});
|
|
24788
24897
|
if (!hasToken) continue;
|
|
24789
24898
|
const outbound = listRoomMessages(db2, room.id, "unread").filter((message) => message.direction === "outbound" && message.toRoomId);
|
|
@@ -25271,8 +25380,9 @@ function registerEscalationRoutes(router) {
|
|
|
25271
25380
|
router.post("/api/rooms/:roomId/escalations", (ctx) => {
|
|
25272
25381
|
const roomId = Number(ctx.params.roomId);
|
|
25273
25382
|
const body = ctx.body || {};
|
|
25274
|
-
|
|
25275
|
-
|
|
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" };
|
|
25276
25386
|
}
|
|
25277
25387
|
if (!body.question || typeof body.question !== "string") {
|
|
25278
25388
|
return { status: 400, error: "question is required" };
|
|
@@ -25280,7 +25390,7 @@ function registerEscalationRoutes(router) {
|
|
|
25280
25390
|
const escalation = createEscalation(
|
|
25281
25391
|
ctx.db,
|
|
25282
25392
|
roomId,
|
|
25283
|
-
|
|
25393
|
+
fromAgentId,
|
|
25284
25394
|
body.question,
|
|
25285
25395
|
body.toAgentId
|
|
25286
25396
|
);
|
|
@@ -25459,6 +25569,8 @@ CREATE TABLE IF NOT EXISTS workers (
|
|
|
25459
25569
|
task_count INTEGER NOT NULL DEFAULT 0,
|
|
25460
25570
|
room_id INTEGER,
|
|
25461
25571
|
agent_state TEXT NOT NULL DEFAULT 'idle',
|
|
25572
|
+
votes_cast INTEGER NOT NULL DEFAULT 0,
|
|
25573
|
+
votes_missed INTEGER NOT NULL DEFAULT 0,
|
|
25462
25574
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
25463
25575
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
25464
25576
|
);
|
|
@@ -25481,6 +25593,7 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|
|
25481
25593
|
queen_quiet_until TEXT,
|
|
25482
25594
|
config TEXT,
|
|
25483
25595
|
chat_session_id TEXT,
|
|
25596
|
+
invite_code TEXT,
|
|
25484
25597
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
25485
25598
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
25486
25599
|
);
|
|
@@ -25665,6 +25778,8 @@ CREATE TABLE IF NOT EXISTS quorum_decisions (
|
|
|
25665
25778
|
threshold TEXT NOT NULL DEFAULT 'majority',
|
|
25666
25779
|
timeout_at DATETIME,
|
|
25667
25780
|
keeper_vote TEXT,
|
|
25781
|
+
min_voters INTEGER NOT NULL DEFAULT 0,
|
|
25782
|
+
sealed INTEGER NOT NULL DEFAULT 0,
|
|
25668
25783
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
25669
25784
|
resolved_at DATETIME
|
|
25670
25785
|
);
|
|
@@ -25850,6 +25965,24 @@ INSERT OR IGNORE INTO schema_version (version) VALUES (1);
|
|
|
25850
25965
|
// src/shared/db-migrations.ts
|
|
25851
25966
|
function runMigrations(database, log = console.log) {
|
|
25852
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
|
+
}
|
|
25853
25986
|
log("Database schema initialized");
|
|
25854
25987
|
}
|
|
25855
25988
|
|
|
@@ -25974,7 +26107,7 @@ function fetchJson(url) {
|
|
|
25974
26107
|
});
|
|
25975
26108
|
});
|
|
25976
26109
|
}
|
|
25977
|
-
async function
|
|
26110
|
+
async function forceCheck() {
|
|
25978
26111
|
try {
|
|
25979
26112
|
const releases = await fetchJson(
|
|
25980
26113
|
"https://api.github.com/repos/quoroom-ai/room/releases?per_page=20"
|
|
@@ -25997,9 +26130,9 @@ async function check() {
|
|
|
25997
26130
|
function initUpdateChecker() {
|
|
25998
26131
|
if (process.env.NODE_ENV === "test") return;
|
|
25999
26132
|
initTimer = setTimeout(() => {
|
|
26000
|
-
void
|
|
26133
|
+
void forceCheck();
|
|
26001
26134
|
pollInterval = setInterval(() => {
|
|
26002
|
-
void
|
|
26135
|
+
void forceCheck();
|
|
26003
26136
|
}, CHECK_INTERVAL);
|
|
26004
26137
|
}, INITIAL_DELAY);
|
|
26005
26138
|
}
|
|
@@ -26017,7 +26150,7 @@ function getUpdateInfo() {
|
|
|
26017
26150
|
return cached;
|
|
26018
26151
|
}
|
|
26019
26152
|
async function simulateUpdate() {
|
|
26020
|
-
if (!cached) await
|
|
26153
|
+
if (!cached) await forceCheck();
|
|
26021
26154
|
cached = {
|
|
26022
26155
|
latestVersion: "99.0.0",
|
|
26023
26156
|
releaseUrl: "https://github.com/quoroom-ai/room/releases",
|
|
@@ -26035,7 +26168,7 @@ var cachedVersion = null;
|
|
|
26035
26168
|
function getVersion3() {
|
|
26036
26169
|
if (cachedVersion) return cachedVersion;
|
|
26037
26170
|
try {
|
|
26038
|
-
cachedVersion = true ? "0.1.
|
|
26171
|
+
cachedVersion = true ? "0.1.12" : null.version;
|
|
26039
26172
|
} catch {
|
|
26040
26173
|
cachedVersion = "unknown";
|
|
26041
26174
|
}
|
|
@@ -26201,6 +26334,10 @@ function registerStatusRoutes(router) {
|
|
|
26201
26334
|
await simulateUpdate();
|
|
26202
26335
|
return { data: { ok: true } };
|
|
26203
26336
|
});
|
|
26337
|
+
router.post("/api/status/check-update", async () => {
|
|
26338
|
+
await forceCheck();
|
|
26339
|
+
return { data: { updateInfo: getUpdateInfo() } };
|
|
26340
|
+
});
|
|
26204
26341
|
router.post("/api/ollama/start", async () => {
|
|
26205
26342
|
const result = await ensureOllamaRunning();
|
|
26206
26343
|
resetOllamaCaches();
|
|
@@ -27039,7 +27176,7 @@ function probeProviderInstalled(provider) {
|
|
|
27039
27176
|
return out.ok ? { installed: true, version: out.stdout || void 0 } : { installed: false };
|
|
27040
27177
|
}
|
|
27041
27178
|
function probeProviderConnected(provider) {
|
|
27042
|
-
const attempts = provider === "codex" ? [["
|
|
27179
|
+
const attempts = provider === "codex" ? [["login", "status"], ["auth", "status"]] : [["auth", "status"], ["login", "status"]];
|
|
27043
27180
|
for (const args of attempts) {
|
|
27044
27181
|
const out = safeExec(provider, args);
|
|
27045
27182
|
if (!out.ok) continue;
|
|
@@ -27548,7 +27685,12 @@ function createWsServer(server) {
|
|
|
27548
27685
|
continue;
|
|
27549
27686
|
}
|
|
27550
27687
|
if (state.channels.has(event.channel)) {
|
|
27551
|
-
ws.send(payload)
|
|
27688
|
+
ws.send(payload, (err) => {
|
|
27689
|
+
if (err) {
|
|
27690
|
+
clients.delete(ws);
|
|
27691
|
+
ws.terminate();
|
|
27692
|
+
}
|
|
27693
|
+
});
|
|
27552
27694
|
}
|
|
27553
27695
|
}
|
|
27554
27696
|
});
|
|
@@ -27709,6 +27851,9 @@ function getCacheControl(filePath, ext) {
|
|
|
27709
27851
|
if (base2 === "sw.js") return "no-cache, no-store, must-revalidate";
|
|
27710
27852
|
if (ext === ".html") return "no-cache, no-store, must-revalidate";
|
|
27711
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
|
+
}
|
|
27712
27857
|
if (normalized.includes("/assets/") && /-[A-Za-z0-9_-]{8,}\./.test(base2)) {
|
|
27713
27858
|
return "public, max-age=31536000, immutable";
|
|
27714
27859
|
}
|
|
@@ -27798,6 +27943,8 @@ function createApiServer(options = {}) {
|
|
|
27798
27943
|
writeTokenFile(dataDir, token, port);
|
|
27799
27944
|
}
|
|
27800
27945
|
const server = import_node_http.default.createServer(async (req, res) => {
|
|
27946
|
+
res.on("error", () => {
|
|
27947
|
+
});
|
|
27801
27948
|
const url = new import_node_url.URL(req.url, `http://${req.headers.host || "localhost"}`);
|
|
27802
27949
|
const pathname = url.pathname;
|
|
27803
27950
|
const origin = req.headers.origin;
|
|
@@ -28038,6 +28185,12 @@ function startServer(options = {}) {
|
|
|
28038
28185
|
}
|
|
28039
28186
|
});
|
|
28040
28187
|
listen();
|
|
28188
|
+
process.on("uncaughtException", (err) => {
|
|
28189
|
+
console.error("[uncaughtException]", err);
|
|
28190
|
+
});
|
|
28191
|
+
process.on("unhandledRejection", (err) => {
|
|
28192
|
+
console.error("[unhandledRejection]", err);
|
|
28193
|
+
});
|
|
28041
28194
|
process.on("SIGINT", () => {
|
|
28042
28195
|
console.error("Shutting down...");
|
|
28043
28196
|
_stopAllLoops();
|