quoroom 0.1.37 → 0.1.39
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 +8 -0
- package/out/mcp/api-server.js +240 -84
- package/out/mcp/cli.js +243 -87
- package/out/mcp/server.js +6 -5
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -457,6 +457,11 @@ npm install # Install dependencies
|
|
|
457
457
|
npm run build # Typecheck + bundle MCP server + build UI
|
|
458
458
|
npm run build:mcp # Bundle MCP server only (esbuild)
|
|
459
459
|
npm run build:ui # Build UI SPA only (Vite)
|
|
460
|
+
npm run dev # Local-only dev (links + room)
|
|
461
|
+
npm run dev:with-cloud # Local dev + cloud (requires ../cloud)
|
|
462
|
+
npm run dev:isolated # Isolated local dev (room :4700 + UI, no cloud)
|
|
463
|
+
npm run dev:isolated:with-cloud # Isolated local dev + cloud
|
|
464
|
+
npm run dev:cloud # Cloud-only (runs ../cloud on :3715)
|
|
460
465
|
npm run dev:ui # UI dev server with hot reload
|
|
461
466
|
npm run typecheck # Type-check only (tsc --noEmit)
|
|
462
467
|
npm test # Run all tests (vitest, fork pool)
|
|
@@ -464,7 +469,10 @@ npm run test:watch # Watch mode
|
|
|
464
469
|
npm run test:e2e # End-to-end tests (Playwright)
|
|
465
470
|
|
|
466
471
|
# Windows
|
|
472
|
+
npm run dev:win # Local-only dev (same as npm run dev)
|
|
473
|
+
npm run dev:with-cloud:win # Local dev + cloud (requires ../cloud)
|
|
467
474
|
npm run dev:isolated:win # Windows equivalent of dev:isolated
|
|
475
|
+
npm run dev:isolated:with-cloud:win # Windows isolated + cloud
|
|
468
476
|
npm run build:windows:local # Local Windows build (PowerShell)
|
|
469
477
|
```
|
|
470
478
|
|
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.39",
|
|
9918
9918
|
description: "Open-source local AI agent framework \u2014 Queen, Workers, Quorum. Experimental research tool.",
|
|
9919
9919
|
main: "./out/mcp/server.js",
|
|
9920
9920
|
bin: {
|
|
@@ -9944,16 +9944,20 @@ var require_package = __commonJS({
|
|
|
9944
9944
|
"kill:quoroom-runtime": "node scripts/kill-quoroom-runtime.js",
|
|
9945
9945
|
"kill:dev-runtime": "npm run kill:dev-ports && npm run kill:quoroom-runtime",
|
|
9946
9946
|
"dev:links": "node scripts/dev-links.js",
|
|
9947
|
-
dev:
|
|
9947
|
+
dev: "node scripts/run-dev.js",
|
|
9948
9948
|
"dev:win": "node scripts/run-dev.js",
|
|
9949
|
+
"dev:with-cloud": "node scripts/run-dev.js --with-cloud",
|
|
9950
|
+
"dev:with-cloud:win": "node scripts/run-dev.js --with-cloud",
|
|
9949
9951
|
"dev:room": "sh -c 'npm run kill:dev-runtime && export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
9950
9952
|
"dev:room:win": "node scripts/dev-room.js --port 4700",
|
|
9951
9953
|
"dev:room:isolated:win": "node scripts/dev-room.js --isolated --port 4700",
|
|
9952
9954
|
"dev:room:isolated": "sh -c 'npm run kill:dev-runtime && export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
9953
9955
|
"dev:room:shared": "npm run kill:dev-runtime && npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
9954
9956
|
"doctor:split": "node scripts/doctor-split.js",
|
|
9955
|
-
"dev:isolated":
|
|
9957
|
+
"dev:isolated": "node scripts/run-dev.js --isolated",
|
|
9956
9958
|
"dev:isolated:win": "node scripts/run-dev.js --isolated",
|
|
9959
|
+
"dev:isolated:with-cloud": "node scripts/run-dev.js --isolated --with-cloud",
|
|
9960
|
+
"dev:isolated:with-cloud:win": "node scripts/run-dev.js --isolated --with-cloud",
|
|
9957
9961
|
"dev:cloud": `sh -c 'npm run kill:ports -- 3715 && cd ../cloud && PORT=3715 CLOUD_PUBLIC_URL=http://127.0.0.1:3715 CLOUD_ALLOWED_ORIGINS='"'"'http://127.0.0.1:3715,http://localhost:3715,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'"'"' npm start'`,
|
|
9958
9962
|
"dev:cloud:win": "node scripts/dev-cloud.js",
|
|
9959
9963
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
@@ -22619,13 +22623,14 @@ Every cycle:
|
|
|
22619
22623
|
1. Check if workers reported results (messages, completed goals)
|
|
22620
22624
|
2. If work is done \u2192 send results to keeper, take next step
|
|
22621
22625
|
3. If work is stuck \u2192 help unblock (new instructions, different approach)
|
|
22622
|
-
4. If
|
|
22623
|
-
5. If
|
|
22626
|
+
4. If no workers exist yet \u2192 create an executor worker first
|
|
22627
|
+
5. If new work is needed \u2192 delegate to a worker with clear instructions, then poke/follow up
|
|
22628
|
+
6. If a decision needs input \u2192 announce it and process objections/votes (announce/object flow)
|
|
22624
22629
|
|
|
22625
22630
|
Talk to the keeper regularly \u2014 they are your client.
|
|
22626
22631
|
|
|
22627
|
-
Do NOT
|
|
22628
|
-
|
|
22632
|
+
Do NOT execute tasks directly (research, form filling, account creation, browser automation).
|
|
22633
|
+
Stay control-plane only: create workers, delegate, monitor, unblock, report.`;
|
|
22629
22634
|
function createRoom2(db2, input) {
|
|
22630
22635
|
const config = { ...DEFAULT_ROOM_CONFIG, ...input.config };
|
|
22631
22636
|
const room = createRoom(db2, input.name, input.goal, config, input.referredByCode);
|
|
@@ -23223,7 +23228,10 @@ async function executeAgent(options) {
|
|
|
23223
23228
|
}
|
|
23224
23229
|
return executeAnthropicApi(options);
|
|
23225
23230
|
}
|
|
23226
|
-
|
|
23231
|
+
if (model === "claude" || model.startsWith("claude-")) {
|
|
23232
|
+
return executeClaude(options);
|
|
23233
|
+
}
|
|
23234
|
+
throw new Error(`Unsupported model "${model}". Configure an explicit supported model (claude, codex, openai:*, anthropic:*, gemini:*).`);
|
|
23227
23235
|
}
|
|
23228
23236
|
async function executeClaude(options) {
|
|
23229
23237
|
const execOpts = {
|
|
@@ -24862,7 +24870,7 @@ var TOOL_WEB_SEARCH = {
|
|
|
24862
24870
|
type: "function",
|
|
24863
24871
|
function: {
|
|
24864
24872
|
name: "quoroom_web_search",
|
|
24865
|
-
description: "Search the web. Returns top 5 results.",
|
|
24873
|
+
description: "Search the web. Returns top 5 results. Queen should delegate this to workers first in control-plane mode.",
|
|
24866
24874
|
parameters: {
|
|
24867
24875
|
type: "object",
|
|
24868
24876
|
properties: {
|
|
@@ -24876,7 +24884,7 @@ var TOOL_WEB_FETCH = {
|
|
|
24876
24884
|
type: "function",
|
|
24877
24885
|
function: {
|
|
24878
24886
|
name: "quoroom_web_fetch",
|
|
24879
|
-
description: "Fetch any URL and return its content as clean markdown.",
|
|
24887
|
+
description: "Fetch any URL and return its content as clean markdown. Queen should delegate this to workers first in control-plane mode.",
|
|
24880
24888
|
parameters: {
|
|
24881
24889
|
type: "object",
|
|
24882
24890
|
properties: {
|
|
@@ -24890,7 +24898,7 @@ var TOOL_BROWSER = {
|
|
|
24890
24898
|
type: "function",
|
|
24891
24899
|
function: {
|
|
24892
24900
|
name: "quoroom_browser",
|
|
24893
|
-
description: "Control a headless browser: navigate, click, fill forms, buy services, register domains, create accounts.",
|
|
24901
|
+
description: "Control a headless browser: navigate, click, fill forms, buy services, register domains, create accounts. Queen should delegate this to workers first in control-plane mode.",
|
|
24894
24902
|
parameters: {
|
|
24895
24903
|
type: "object",
|
|
24896
24904
|
properties: {
|
|
@@ -25338,6 +25346,12 @@ async function deliverQueenMessage(db2, roomId, question) {
|
|
|
25338
25346
|
}
|
|
25339
25347
|
|
|
25340
25348
|
// src/shared/agent-loop.ts
|
|
25349
|
+
var QUEEN_EXECUTION_TOOLS = /* @__PURE__ */ new Set([
|
|
25350
|
+
"quoroom_web_search",
|
|
25351
|
+
"quoroom_web_fetch",
|
|
25352
|
+
"quoroom_browser"
|
|
25353
|
+
]);
|
|
25354
|
+
var QUEEN_POLICY_WIP_HINT = "[policy] Queen control-plane mode: delegate execution tasks to workers with quoroom_delegate_task, then monitor, unblock, and report outcomes. Avoid direct web/browser execution.";
|
|
25341
25355
|
function isInQuietHours(from14, until) {
|
|
25342
25356
|
const now = /* @__PURE__ */ new Date();
|
|
25343
25357
|
const nowMins = now.getHours() * 60 + now.getMinutes();
|
|
@@ -25358,7 +25372,34 @@ function msUntilQuietEnd(until) {
|
|
|
25358
25372
|
if (end <= now) end.setDate(end.getDate() + 1);
|
|
25359
25373
|
return end.getTime() - now.getTime();
|
|
25360
25374
|
}
|
|
25375
|
+
function nextAutoExecutorName(workers) {
|
|
25376
|
+
const names = new Set(workers.map((w) => w.name.toLowerCase()));
|
|
25377
|
+
let idx = 1;
|
|
25378
|
+
while (names.has(`executor-${idx}`)) idx++;
|
|
25379
|
+
return `executor-${idx}`;
|
|
25380
|
+
}
|
|
25381
|
+
function extractToolNameFromConsoleLog(content) {
|
|
25382
|
+
const usingMatch = content.match(/(?:Using|→)\s*([a-zA-Z0-9_]+)/);
|
|
25383
|
+
if (usingMatch?.[1]) return usingMatch[1];
|
|
25384
|
+
const callMatch = content.match(/^([a-zA-Z0-9_]+)\s*\(/);
|
|
25385
|
+
return callMatch?.[1] ?? null;
|
|
25386
|
+
}
|
|
25387
|
+
function resolveWorkerExecutionModel(db2, roomId, worker) {
|
|
25388
|
+
const explicit = worker.model?.trim();
|
|
25389
|
+
if (explicit) return explicit;
|
|
25390
|
+
const room = getRoom(db2, roomId);
|
|
25391
|
+
if (!room) return null;
|
|
25392
|
+
const roomModel = room.workerModel?.trim();
|
|
25393
|
+
if (!roomModel) return null;
|
|
25394
|
+
if (roomModel !== "queen") return roomModel;
|
|
25395
|
+
if (!room.queenWorkerId) return null;
|
|
25396
|
+
if (room.queenWorkerId === worker.id) return null;
|
|
25397
|
+
const queen = getWorker(db2, room.queenWorkerId);
|
|
25398
|
+
const queenModel = queen?.model?.trim();
|
|
25399
|
+
return queenModel || null;
|
|
25400
|
+
}
|
|
25361
25401
|
var runningLoops = /* @__PURE__ */ new Map();
|
|
25402
|
+
var launchedRoomIds = /* @__PURE__ */ new Set();
|
|
25362
25403
|
var RateLimitError = class extends Error {
|
|
25363
25404
|
constructor(info) {
|
|
25364
25405
|
super(`Rate limited: wait ${Math.round(info.waitMs / 1e3)}s`);
|
|
@@ -25481,12 +25522,29 @@ function pauseAgent(db2, workerId) {
|
|
|
25481
25522
|
}
|
|
25482
25523
|
updateAgentState(db2, workerId, "idle");
|
|
25483
25524
|
}
|
|
25525
|
+
function setRoomLaunchEnabled(roomId, enabled) {
|
|
25526
|
+
if (enabled) {
|
|
25527
|
+
launchedRoomIds.add(roomId);
|
|
25528
|
+
return;
|
|
25529
|
+
}
|
|
25530
|
+
launchedRoomIds.delete(roomId);
|
|
25531
|
+
}
|
|
25532
|
+
function isRoomLaunchEnabled(roomId) {
|
|
25533
|
+
return launchedRoomIds.has(roomId);
|
|
25534
|
+
}
|
|
25535
|
+
function clearRoomLaunchState() {
|
|
25536
|
+
launchedRoomIds.clear();
|
|
25537
|
+
}
|
|
25484
25538
|
function triggerAgent(db2, roomId, workerId, options) {
|
|
25485
25539
|
const loop = runningLoops.get(workerId);
|
|
25486
25540
|
if (loop?.running) {
|
|
25487
25541
|
if (loop.waitAbort) loop.waitAbort.abort();
|
|
25488
25542
|
return;
|
|
25489
25543
|
}
|
|
25544
|
+
const canColdStart = options?.allowColdStart === true || isRoomLaunchEnabled(roomId);
|
|
25545
|
+
if (!canColdStart) {
|
|
25546
|
+
return;
|
|
25547
|
+
}
|
|
25490
25548
|
startAgentLoop(db2, roomId, workerId, options).catch((err) => {
|
|
25491
25549
|
const msg = err instanceof Error ? err.message : String(err);
|
|
25492
25550
|
console.error(`Agent loop failed for worker ${workerId}: ${msg}`);
|
|
@@ -25534,7 +25592,7 @@ async function runCycle(db2, roomId, worker, maxTurns, options, abortSignal) {
|
|
|
25534
25592
|
void 0,
|
|
25535
25593
|
worker.id
|
|
25536
25594
|
);
|
|
25537
|
-
const model =
|
|
25595
|
+
const model = resolveWorkerExecutionModel(db2, roomId, worker);
|
|
25538
25596
|
const cycle = createWorkerCycle(db2, worker.id, roomId, model);
|
|
25539
25597
|
const logBuffer2 = createCycleLogBuffer(
|
|
25540
25598
|
cycle.id,
|
|
@@ -25543,6 +25601,23 @@ async function runCycle(db2, roomId, worker, maxTurns, options, abortSignal) {
|
|
|
25543
25601
|
);
|
|
25544
25602
|
options?.onCycleLifecycle?.("created", cycle.id, roomId);
|
|
25545
25603
|
try {
|
|
25604
|
+
if (!model) {
|
|
25605
|
+
const msg = "No model configured for this worker. Set an explicit worker model or room worker model.";
|
|
25606
|
+
logBuffer2.addSynthetic("error", msg);
|
|
25607
|
+
logBuffer2.flush();
|
|
25608
|
+
completeWorkerCycle(db2, cycle.id, msg, void 0);
|
|
25609
|
+
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
25610
|
+
logRoomActivity(
|
|
25611
|
+
db2,
|
|
25612
|
+
roomId,
|
|
25613
|
+
"error",
|
|
25614
|
+
`Agent cycle failed (${worker.name}): model is not configured`,
|
|
25615
|
+
msg,
|
|
25616
|
+
worker.id
|
|
25617
|
+
);
|
|
25618
|
+
updateAgentState(db2, worker.id, "idle");
|
|
25619
|
+
return msg;
|
|
25620
|
+
}
|
|
25546
25621
|
const provider = getModelProvider(model);
|
|
25547
25622
|
if (provider === "openai_api" || provider === "anthropic_api" || provider === "gemini_api") {
|
|
25548
25623
|
const apiKeyCheck = resolveApiKeyForModel(db2, roomId, model);
|
|
@@ -25569,10 +25644,44 @@ async function runCycle(db2, roomId, worker, maxTurns, options, abortSignal) {
|
|
|
25569
25644
|
status: g.status,
|
|
25570
25645
|
assignedWorkerId: g.assignedWorkerId
|
|
25571
25646
|
}));
|
|
25572
|
-
|
|
25647
|
+
let roomWorkers = listRoomWorkers(db2, roomId);
|
|
25648
|
+
const isQueen = worker.id === status2.room.queenWorkerId;
|
|
25573
25649
|
const unreadMessages = listRoomMessages(db2, roomId, "unread").slice(0, 5);
|
|
25650
|
+
if (isQueen) {
|
|
25651
|
+
const nonQueenWorkers = roomWorkers.filter((w) => w.id !== worker.id);
|
|
25652
|
+
if (nonQueenWorkers.length === 0) {
|
|
25653
|
+
const autoName = nextAutoExecutorName(roomWorkers);
|
|
25654
|
+
const executorPreset = WORKER_ROLE_PRESETS.executor;
|
|
25655
|
+
const inheritedModel = status2.room.workerModel === "queen" ? model : status2.room.workerModel?.trim();
|
|
25656
|
+
if (!inheritedModel) {
|
|
25657
|
+
const err = "Auto-create skipped: no worker model configured for executor.";
|
|
25658
|
+
logRoomActivity(db2, roomId, "error", err, "Set room worker model or queen model first.", worker.id);
|
|
25659
|
+
logBuffer2.addSynthetic("error", err);
|
|
25660
|
+
} else {
|
|
25661
|
+
createWorker(db2, {
|
|
25662
|
+
name: autoName,
|
|
25663
|
+
role: "executor",
|
|
25664
|
+
roomId,
|
|
25665
|
+
description: "Auto-created executor for queen-delegated execution work.",
|
|
25666
|
+
systemPrompt: "You are the room executor. Complete delegated tasks end-to-end, report concrete results, and save progress with quoroom_save_wip.",
|
|
25667
|
+
model: inheritedModel,
|
|
25668
|
+
cycleGapMs: executorPreset?.cycleGapMs,
|
|
25669
|
+
maxTurns: executorPreset?.maxTurns
|
|
25670
|
+
});
|
|
25671
|
+
logRoomActivity(
|
|
25672
|
+
db2,
|
|
25673
|
+
roomId,
|
|
25674
|
+
"system",
|
|
25675
|
+
`Auto-created worker "${autoName}" for delegation-first execution.`,
|
|
25676
|
+
"Model B (soft): queen coordinates, workers execute.",
|
|
25677
|
+
worker.id
|
|
25678
|
+
);
|
|
25679
|
+
logBuffer2.addSynthetic("system", `Auto-created worker "${autoName}" because queen had no executors.`);
|
|
25680
|
+
roomWorkers = listRoomWorkers(db2, roomId);
|
|
25681
|
+
}
|
|
25682
|
+
}
|
|
25683
|
+
}
|
|
25574
25684
|
const rolePreset = worker.role ? WORKER_ROLE_PRESETS[worker.role] : void 0;
|
|
25575
|
-
const isQueen = worker.id === status2.room.queenWorkerId;
|
|
25576
25685
|
const namePrefix = worker.name ? `Your name is ${worker.name}.
|
|
25577
25686
|
|
|
25578
25687
|
` : "";
|
|
@@ -25661,6 +25770,14 @@ At the end of this cycle, call quoroom_save_wip to save your updated position.`)
|
|
|
25661
25770
|
if (status2.room.goal) {
|
|
25662
25771
|
contextParts.push(`## Room Objective
|
|
25663
25772
|
${status2.room.goal}`);
|
|
25773
|
+
}
|
|
25774
|
+
if (isQueen) {
|
|
25775
|
+
contextParts.push(`## Queen Controller Contract (Model B)
|
|
25776
|
+
- You are the control plane: create workers, delegate tasks, and monitor delivery.
|
|
25777
|
+
- If there are no workers besides you, create one executor first.
|
|
25778
|
+
- Delegate all execution via quoroom_delegate_task and follow up with worker messages/pokes.
|
|
25779
|
+
- Keep governance active: use quoroom_announce for decisions and process objections/votes.
|
|
25780
|
+
- Do not perform execution tasks directly unless strictly unavoidable.`);
|
|
25664
25781
|
}
|
|
25665
25782
|
if (goalUpdates.length > 0) {
|
|
25666
25783
|
const workerMap = new Map(roomWorkers.map((w) => [w.id, w.name]));
|
|
@@ -25773,9 +25890,34 @@ ${unreadMessages.map(
|
|
|
25773
25890
|
const needsQueenTools = model === "openai" || model.startsWith("openai:") || model === "anthropic" || model.startsWith("anthropic:") || model.startsWith("claude-api:");
|
|
25774
25891
|
const roleToolDefs = isQueen ? QUEEN_TOOLS : WORKER_TOOLS;
|
|
25775
25892
|
const filteredToolDefs = allowSet ? roleToolDefs.filter((t) => allowSet.has(t.function.name)) : roleToolDefs;
|
|
25893
|
+
const queenExecutionToolsUsed = /* @__PURE__ */ new Set();
|
|
25894
|
+
const trackQueenExecutionTool = (toolName) => {
|
|
25895
|
+
if (!isQueen || !toolName) return;
|
|
25896
|
+
if (QUEEN_EXECUTION_TOOLS.has(toolName)) queenExecutionToolsUsed.add(toolName);
|
|
25897
|
+
};
|
|
25898
|
+
const persistQueenPolicyDeviation = () => {
|
|
25899
|
+
if (!isQueen || queenExecutionToolsUsed.size === 0) return;
|
|
25900
|
+
const used = [...queenExecutionToolsUsed].sort().join(", ");
|
|
25901
|
+
logRoomActivity(
|
|
25902
|
+
db2,
|
|
25903
|
+
roomId,
|
|
25904
|
+
"system",
|
|
25905
|
+
`Queen policy deviation: execution tool use detected (${used}).`,
|
|
25906
|
+
"Model B (soft): queen should delegate execution to workers and remain control-plane focused.",
|
|
25907
|
+
worker.id
|
|
25908
|
+
);
|
|
25909
|
+
const fresh = getWorker(db2, worker.id);
|
|
25910
|
+
const existing = fresh?.wip?.trim() ?? "";
|
|
25911
|
+
if (existing.includes(QUEEN_POLICY_WIP_HINT)) return;
|
|
25912
|
+
const nextWip = existing ? `${existing}
|
|
25913
|
+
|
|
25914
|
+
${QUEEN_POLICY_WIP_HINT}` : QUEEN_POLICY_WIP_HINT;
|
|
25915
|
+
updateWorkerWip(db2, worker.id, nextWip.slice(0, 2e3));
|
|
25916
|
+
};
|
|
25776
25917
|
const apiToolOpts = needsQueenTools ? {
|
|
25777
25918
|
toolDefs: filteredToolDefs,
|
|
25778
25919
|
onToolCall: async (toolName, args) => {
|
|
25920
|
+
trackQueenExecutionTool(toolName);
|
|
25779
25921
|
logBuffer2.addSynthetic("tool_call", `\u2192 ${toolName}(${JSON.stringify(args)})`);
|
|
25780
25922
|
const result2 = await executeQueenTool(db2, roomId, worker.id, toolName, args);
|
|
25781
25923
|
logBuffer2.addSynthetic("tool_result", result2.content);
|
|
@@ -25789,7 +25931,12 @@ ${unreadMessages.map(
|
|
|
25789
25931
|
apiKey,
|
|
25790
25932
|
timeoutMs: worker.role === "executor" ? 30 * 60 * 1e3 : 15 * 60 * 1e3,
|
|
25791
25933
|
maxTurns: maxTurns ?? 50,
|
|
25792
|
-
onConsoleLog:
|
|
25934
|
+
onConsoleLog: (entry) => {
|
|
25935
|
+
if (entry.entryType === "tool_call") {
|
|
25936
|
+
trackQueenExecutionTool(extractToolNameFromConsoleLog(entry.content));
|
|
25937
|
+
}
|
|
25938
|
+
logBuffer2.onConsoleLog(entry);
|
|
25939
|
+
},
|
|
25793
25940
|
// CLI models: block non-quoroom MCP tools (daymon, etc.)
|
|
25794
25941
|
disallowedTools: isCli ? "mcp__daymon*" : void 0,
|
|
25795
25942
|
// CLI models: bypass permission prompts for headless operation
|
|
@@ -25822,6 +25969,7 @@ ${unreadMessages.map(
|
|
|
25822
25969
|
completeWorkerCycle(db2, cycle.id, canceledMessage, result.usage);
|
|
25823
25970
|
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
25824
25971
|
updateAgentState(db2, worker.id, "idle");
|
|
25972
|
+
persistQueenPolicyDeviation();
|
|
25825
25973
|
return result.output;
|
|
25826
25974
|
}
|
|
25827
25975
|
const rateLimitInfo = checkRateLimit(result);
|
|
@@ -25850,6 +25998,7 @@ ${unreadMessages.map(
|
|
|
25850
25998
|
logBuffer2.flush();
|
|
25851
25999
|
}
|
|
25852
26000
|
}
|
|
26001
|
+
persistQueenPolicyDeviation();
|
|
25853
26002
|
return result.output;
|
|
25854
26003
|
}
|
|
25855
26004
|
if (isCli && result.sessionId) {
|
|
@@ -25858,6 +26007,7 @@ ${unreadMessages.map(
|
|
|
25858
26007
|
if (result.output && model !== "claude" && !model.startsWith("codex")) {
|
|
25859
26008
|
logBuffer2.addSynthetic("assistant_text", result.output);
|
|
25860
26009
|
}
|
|
26010
|
+
persistQueenPolicyDeviation();
|
|
25861
26011
|
logBuffer2.addSynthetic("system", "Cycle completed");
|
|
25862
26012
|
if (result.usage && (result.usage.inputTokens > 0 || result.usage.outputTokens > 0)) {
|
|
25863
26013
|
logBuffer2.addSynthetic("system", `Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`);
|
|
@@ -25908,6 +26058,7 @@ function _stopAllLoops() {
|
|
|
25908
26058
|
if (loop.cycleAbort) loop.cycleAbort.abort();
|
|
25909
26059
|
}
|
|
25910
26060
|
runningLoops.clear();
|
|
26061
|
+
clearRoomLaunchState();
|
|
25911
26062
|
}
|
|
25912
26063
|
|
|
25913
26064
|
// src/server/cloud.ts
|
|
@@ -28277,13 +28428,10 @@ async function executeClerkTool(db2, toolName, args, ctx) {
|
|
|
28277
28428
|
return { content: `Deleted room "${room.name}" (#${room.id}).` };
|
|
28278
28429
|
}
|
|
28279
28430
|
case "quoroom_start_queen": {
|
|
28280
|
-
|
|
28281
|
-
|
|
28282
|
-
|
|
28283
|
-
|
|
28284
|
-
stopRoomRuntime(db2, room.id, "Runtime reset before queen start");
|
|
28285
|
-
triggerAgent(db2, room.id, room.queenWorkerId);
|
|
28286
|
-
return { content: `Started queen in "${room.name}" (#${room.id}).` };
|
|
28431
|
+
return {
|
|
28432
|
+
content: "Error: direct queen start is disabled. Start the room manually from the Room controls.",
|
|
28433
|
+
isError: true
|
|
28434
|
+
};
|
|
28287
28435
|
}
|
|
28288
28436
|
case "quoroom_stop_queen": {
|
|
28289
28437
|
const room = resolveRoom(db2, args);
|
|
@@ -30484,6 +30632,9 @@ async function syncCloudRoomMessages(db2) {
|
|
|
30484
30632
|
}
|
|
30485
30633
|
function startServerRuntime(db2) {
|
|
30486
30634
|
stopServerRuntime();
|
|
30635
|
+
clearRoomLaunchState();
|
|
30636
|
+
const cleanedCycles = cleanupStaleCycles(db2);
|
|
30637
|
+
if (cleanedCycles > 0) console.log(`Cleaned up ${cleanedCycles} stale worker cycles`);
|
|
30487
30638
|
ensureClerkContactCheckTasks(db2);
|
|
30488
30639
|
refreshCronJobs(db2);
|
|
30489
30640
|
runDueOneTimeTasks(db2);
|
|
@@ -30511,30 +30662,8 @@ function startServerRuntime(db2) {
|
|
|
30511
30662
|
clerkAlertTimer = setInterval(() => {
|
|
30512
30663
|
queueClerkAlertRelay(db2);
|
|
30513
30664
|
}, CLERK_ALERT_RELAY_MS);
|
|
30514
|
-
resumeActiveQueens(db2);
|
|
30515
30665
|
startCommentaryEngine(db2);
|
|
30516
30666
|
}
|
|
30517
|
-
function makeCycleCallbacks() {
|
|
30518
|
-
return {
|
|
30519
|
-
onCycleLogEntry: (entry) => {
|
|
30520
|
-
eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry);
|
|
30521
|
-
},
|
|
30522
|
-
onCycleLifecycle: (event, cycleId, roomId) => {
|
|
30523
|
-
eventBus.emit(`room:${roomId}`, `cycle:${event}`, { cycleId, roomId });
|
|
30524
|
-
}
|
|
30525
|
-
};
|
|
30526
|
-
}
|
|
30527
|
-
function resumeActiveQueens(db2) {
|
|
30528
|
-
const cleaned = cleanupStaleCycles(db2);
|
|
30529
|
-
if (cleaned > 0) console.log(`Cleaned up ${cleaned} stale worker cycles`);
|
|
30530
|
-
const rooms = listRooms(db2, "active");
|
|
30531
|
-
const callbacks = makeCycleCallbacks();
|
|
30532
|
-
for (const room of rooms) {
|
|
30533
|
-
if (!room.queenWorkerId) continue;
|
|
30534
|
-
console.log(`Auto-resuming queen for room "${room.name}" (#${room.id})`);
|
|
30535
|
-
triggerAgent(db2, room.id, room.queenWorkerId, callbacks);
|
|
30536
|
-
}
|
|
30537
|
-
}
|
|
30538
30667
|
function stopServerRuntime() {
|
|
30539
30668
|
stopCommentaryEngine();
|
|
30540
30669
|
if (schedulerTimer) clearInterval(schedulerTimer);
|
|
@@ -30579,6 +30708,16 @@ function emitQueenState(roomId, running) {
|
|
|
30579
30708
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30580
30709
|
});
|
|
30581
30710
|
}
|
|
30711
|
+
function makeCycleCallbacks(roomId) {
|
|
30712
|
+
return {
|
|
30713
|
+
onCycleLogEntry: (entry) => {
|
|
30714
|
+
eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry);
|
|
30715
|
+
},
|
|
30716
|
+
onCycleLifecycle: (event, cycleId, _roomId) => {
|
|
30717
|
+
eventBus.emit(`room:${roomId}`, `cycle:${event}`, { cycleId, roomId });
|
|
30718
|
+
}
|
|
30719
|
+
};
|
|
30720
|
+
}
|
|
30582
30721
|
function getLocalReferredRooms(db2, roomId) {
|
|
30583
30722
|
const keeperCode = (getSetting(db2, "keeper_referral_code") ?? "").trim();
|
|
30584
30723
|
if (!keeperCode) return [];
|
|
@@ -30792,11 +30931,8 @@ function registerRoomRoutes(router) {
|
|
|
30792
30931
|
}
|
|
30793
30932
|
}
|
|
30794
30933
|
if (updates.status === "stopped") {
|
|
30795
|
-
|
|
30796
|
-
|
|
30797
|
-
updateAgentState(ctx.db, w.id, "idle");
|
|
30798
|
-
pauseAgent(ctx.db, w.id);
|
|
30799
|
-
}
|
|
30934
|
+
setRoomLaunchEnabled(roomId, false);
|
|
30935
|
+
stopRoomRuntime(ctx.db, roomId, "Room archived");
|
|
30800
30936
|
logRoomActivity(ctx.db, roomId, "system", "Room archived");
|
|
30801
30937
|
}
|
|
30802
30938
|
const updated = getRoom(ctx.db, roomId);
|
|
@@ -30811,21 +30947,59 @@ function registerRoomRoutes(router) {
|
|
|
30811
30947
|
const roomId = Number(ctx.params.id);
|
|
30812
30948
|
try {
|
|
30813
30949
|
pauseRoom(ctx.db, roomId);
|
|
30814
|
-
|
|
30815
|
-
|
|
30816
|
-
pauseAgent(ctx.db, w.id);
|
|
30817
|
-
}
|
|
30950
|
+
setRoomLaunchEnabled(roomId, false);
|
|
30951
|
+
stopRoomRuntime(ctx.db, roomId, "Room stopped by keeper");
|
|
30818
30952
|
eventBus.emit(`room:${roomId}`, "room:paused", { roomId });
|
|
30953
|
+
eventBus.emit(`room:${roomId}`, "room:queen_stopped", { roomId, running: false });
|
|
30954
|
+
emitQueenState(roomId, false);
|
|
30819
30955
|
emitRoomsUpdated("room_paused", { roomId });
|
|
30820
30956
|
return { data: { ok: true } };
|
|
30821
30957
|
} catch (e) {
|
|
30822
30958
|
return { status: 404, error: e.message };
|
|
30823
30959
|
}
|
|
30824
30960
|
});
|
|
30961
|
+
router.post("/api/rooms/:id/start", (ctx) => {
|
|
30962
|
+
const roomId = Number(ctx.params.id);
|
|
30963
|
+
const room = getRoom(ctx.db, roomId);
|
|
30964
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
30965
|
+
if (room.status === "stopped") return { status: 400, error: "Room is stopped" };
|
|
30966
|
+
if (!room.queenWorkerId) return { status: 400, error: "No queen worker" };
|
|
30967
|
+
if (room.status !== "active") {
|
|
30968
|
+
updateRoom(ctx.db, roomId, { status: "active" });
|
|
30969
|
+
}
|
|
30970
|
+
setRoomLaunchEnabled(roomId, true);
|
|
30971
|
+
stopRoomRuntime(ctx.db, roomId, "Runtime reset before room start");
|
|
30972
|
+
triggerAgent(ctx.db, roomId, room.queenWorkerId, {
|
|
30973
|
+
...makeCycleCallbacks(roomId),
|
|
30974
|
+
allowColdStart: true
|
|
30975
|
+
});
|
|
30976
|
+
eventBus.emit(`room:${roomId}`, "room:started", { roomId });
|
|
30977
|
+
eventBus.emit(`room:${roomId}`, "room:queen_started", { roomId, running: true });
|
|
30978
|
+
emitQueenState(roomId, true);
|
|
30979
|
+
emitRoomsUpdated("room_started", { roomId });
|
|
30980
|
+
return { data: { ok: true, running: true } };
|
|
30981
|
+
});
|
|
30982
|
+
router.post("/api/rooms/:id/stop", (ctx) => {
|
|
30983
|
+
const roomId = Number(ctx.params.id);
|
|
30984
|
+
const room = getRoom(ctx.db, roomId);
|
|
30985
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
30986
|
+
setRoomLaunchEnabled(roomId, false);
|
|
30987
|
+
stopRoomRuntime(ctx.db, roomId, "Room stopped by keeper");
|
|
30988
|
+
if (room.status !== "stopped") {
|
|
30989
|
+
updateRoom(ctx.db, roomId, { status: "paused" });
|
|
30990
|
+
}
|
|
30991
|
+
eventBus.emit(`room:${roomId}`, "room:stopped", { roomId });
|
|
30992
|
+
eventBus.emit(`room:${roomId}`, "room:paused", { roomId });
|
|
30993
|
+
eventBus.emit(`room:${roomId}`, "room:queen_stopped", { roomId, running: false });
|
|
30994
|
+
emitQueenState(roomId, false);
|
|
30995
|
+
emitRoomsUpdated("room_paused", { roomId });
|
|
30996
|
+
return { data: { ok: true, running: false } };
|
|
30997
|
+
});
|
|
30825
30998
|
router.post("/api/rooms/:id/restart", (ctx) => {
|
|
30826
30999
|
const roomId = Number(ctx.params.id);
|
|
30827
31000
|
const { goal } = ctx.body || {};
|
|
30828
31001
|
try {
|
|
31002
|
+
setRoomLaunchEnabled(roomId, false);
|
|
30829
31003
|
restartRoom(ctx.db, roomId, goal);
|
|
30830
31004
|
eventBus.emit(`room:${roomId}`, "room:restarted", { roomId });
|
|
30831
31005
|
emitRoomsUpdated("room_restarted", { roomId });
|
|
@@ -30853,30 +31027,11 @@ function registerRoomRoutes(router) {
|
|
|
30853
31027
|
}
|
|
30854
31028
|
};
|
|
30855
31029
|
});
|
|
30856
|
-
router.post("/api/rooms/:id/queen/start", (
|
|
30857
|
-
|
|
30858
|
-
const room = getRoom(ctx.db, roomId);
|
|
30859
|
-
if (!room) return { status: 404, error: "Room not found" };
|
|
30860
|
-
if (room.status !== "active") return { status: 400, error: "Room is not active" };
|
|
30861
|
-
if (!room.queenWorkerId) return { status: 400, error: "No queen worker" };
|
|
30862
|
-
stopRoomRuntime(ctx.db, roomId, "Runtime reset before queen start");
|
|
30863
|
-
triggerAgent(ctx.db, roomId, room.queenWorkerId, {
|
|
30864
|
-
onCycleLogEntry: (entry) => eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry),
|
|
30865
|
-
onCycleLifecycle: (event, cycleId) => eventBus.emit(`room:${roomId}`, `cycle:${event}`, { cycleId, roomId })
|
|
30866
|
-
});
|
|
30867
|
-
eventBus.emit(`room:${roomId}`, "room:queen_started", { roomId, running: true });
|
|
30868
|
-
emitQueenState(roomId, true);
|
|
30869
|
-
return { data: { ok: true, running: true } };
|
|
31030
|
+
router.post("/api/rooms/:id/queen/start", (_ctx) => {
|
|
31031
|
+
return { status: 410, error: "Deprecated. Use POST /api/rooms/:id/start" };
|
|
30870
31032
|
});
|
|
30871
|
-
router.post("/api/rooms/:id/queen/stop", (
|
|
30872
|
-
|
|
30873
|
-
const room = getRoom(ctx.db, roomId);
|
|
30874
|
-
if (!room) return { status: 404, error: "Room not found" };
|
|
30875
|
-
if (!room.queenWorkerId) return { status: 400, error: "No queen worker" };
|
|
30876
|
-
stopRoomRuntime(ctx.db, roomId, "Queen stopped by keeper");
|
|
30877
|
-
eventBus.emit(`room:${roomId}`, "room:queen_stopped", { roomId, running: false });
|
|
30878
|
-
emitQueenState(roomId, false);
|
|
30879
|
-
return { data: { ok: true, running: false } };
|
|
31033
|
+
router.post("/api/rooms/:id/queen/stop", (_ctx) => {
|
|
31034
|
+
return { status: 410, error: "Deprecated. Use POST /api/rooms/:id/stop" };
|
|
30880
31035
|
});
|
|
30881
31036
|
router.get("/api/rooms/:id/cycles", (ctx) => {
|
|
30882
31037
|
const roomId = Number(ctx.params.id);
|
|
@@ -30890,8 +31045,8 @@ function registerRoomRoutes(router) {
|
|
|
30890
31045
|
const today = getRoomTokenUsageToday(ctx.db, roomId);
|
|
30891
31046
|
const room = getRoom(ctx.db, roomId);
|
|
30892
31047
|
const queenWorker = room?.queenWorkerId ? getWorker(ctx.db, room.queenWorkerId) : null;
|
|
30893
|
-
const model = queenWorker?.model ?? room?.workerModel ??
|
|
30894
|
-
const isApiModel = model.startsWith("openai") || model.startsWith("anthropic") || model.startsWith("claude-api");
|
|
31048
|
+
const model = queenWorker?.model ?? room?.workerModel ?? null;
|
|
31049
|
+
const isApiModel = !!model && (model.startsWith("openai") || model.startsWith("anthropic") || model.startsWith("claude-api"));
|
|
30895
31050
|
return { data: { total, today, isApiModel } };
|
|
30896
31051
|
});
|
|
30897
31052
|
router.get("/api/cycles/:id/logs", (ctx) => {
|
|
@@ -30904,10 +31059,8 @@ function registerRoomRoutes(router) {
|
|
|
30904
31059
|
router.delete("/api/rooms/:id", (ctx) => {
|
|
30905
31060
|
const roomId = Number(ctx.params.id);
|
|
30906
31061
|
try {
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
pauseAgent(ctx.db, w.id);
|
|
30910
|
-
}
|
|
31062
|
+
setRoomLaunchEnabled(roomId, false);
|
|
31063
|
+
stopRoomRuntime(ctx.db, roomId, "Room deleted");
|
|
30911
31064
|
deleteRoom2(ctx.db, roomId);
|
|
30912
31065
|
eventBus.emit(`room:${roomId}`, "room:deleted", { roomId });
|
|
30913
31066
|
emitRoomsUpdated("room_deleted", { roomId });
|
|
@@ -30973,6 +31126,9 @@ function registerWorkerRoutes(router) {
|
|
|
30973
31126
|
const room = getRoom(ctx.db, worker.roomId);
|
|
30974
31127
|
if (!room) return { status: 404, error: "Room not found" };
|
|
30975
31128
|
if (room.status !== "active") return { status: 400, error: "Room is not active" };
|
|
31129
|
+
if (!isRoomLaunchEnabled(worker.roomId)) {
|
|
31130
|
+
return { status: 409, error: "Room runtime is not started. Start the room first." };
|
|
31131
|
+
}
|
|
30976
31132
|
triggerAgent(ctx.db, worker.roomId, id, {
|
|
30977
31133
|
onCycleLogEntry: (entry) => eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry),
|
|
30978
31134
|
onCycleLifecycle: (event, cycleId) => eventBus.emit(`room:${worker.roomId}`, `cycle:${event}`, { cycleId, roomId: worker.roomId })
|
|
@@ -32516,7 +32672,7 @@ function semverGt(a, b) {
|
|
|
32516
32672
|
}
|
|
32517
32673
|
function getCurrentVersion() {
|
|
32518
32674
|
try {
|
|
32519
|
-
return true ? "0.1.
|
|
32675
|
+
return true ? "0.1.39" : null.version;
|
|
32520
32676
|
} catch {
|
|
32521
32677
|
return "0.0.0";
|
|
32522
32678
|
}
|
|
@@ -32697,7 +32853,7 @@ var cachedVersion = null;
|
|
|
32697
32853
|
function getVersion3() {
|
|
32698
32854
|
if (cachedVersion) return cachedVersion;
|
|
32699
32855
|
try {
|
|
32700
|
-
cachedVersion = true ? "0.1.
|
|
32856
|
+
cachedVersion = true ? "0.1.39" : null.version;
|
|
32701
32857
|
} catch {
|
|
32702
32858
|
cachedVersion = "unknown";
|
|
32703
32859
|
}
|
package/out/mcp/cli.js
CHANGED
|
@@ -26937,7 +26937,10 @@ async function executeAgent(options) {
|
|
|
26937
26937
|
}
|
|
26938
26938
|
return executeAnthropicApi(options);
|
|
26939
26939
|
}
|
|
26940
|
-
|
|
26940
|
+
if (model === "claude" || model.startsWith("claude-")) {
|
|
26941
|
+
return executeClaude(options);
|
|
26942
|
+
}
|
|
26943
|
+
throw new Error(`Unsupported model "${model}". Configure an explicit supported model (claude, codex, openai:*, anthropic:*, gemini:*).`);
|
|
26941
26944
|
}
|
|
26942
26945
|
async function executeClaude(options) {
|
|
26943
26946
|
const execOpts = {
|
|
@@ -49454,13 +49457,14 @@ Every cycle:
|
|
|
49454
49457
|
1. Check if workers reported results (messages, completed goals)
|
|
49455
49458
|
2. If work is done \u2192 send results to keeper, take next step
|
|
49456
49459
|
3. If work is stuck \u2192 help unblock (new instructions, different approach)
|
|
49457
|
-
4. If
|
|
49458
|
-
5. If
|
|
49460
|
+
4. If no workers exist yet \u2192 create an executor worker first
|
|
49461
|
+
5. If new work is needed \u2192 delegate to a worker with clear instructions, then poke/follow up
|
|
49462
|
+
6. If a decision needs input \u2192 announce it and process objections/votes (announce/object flow)
|
|
49459
49463
|
|
|
49460
49464
|
Talk to the keeper regularly \u2014 they are your client.
|
|
49461
49465
|
|
|
49462
|
-
Do NOT
|
|
49463
|
-
|
|
49466
|
+
Do NOT execute tasks directly (research, form filling, account creation, browser automation).
|
|
49467
|
+
Stay control-plane only: create workers, delegate, monitor, unblock, report.`;
|
|
49464
49468
|
}
|
|
49465
49469
|
});
|
|
49466
49470
|
|
|
@@ -52537,7 +52541,7 @@ var server_exports = {};
|
|
|
52537
52541
|
async function main() {
|
|
52538
52542
|
const server = new McpServer({
|
|
52539
52543
|
name: "quoroom",
|
|
52540
|
-
version: true ? "0.1.
|
|
52544
|
+
version: true ? "0.1.39" : "0.0.0"
|
|
52541
52545
|
});
|
|
52542
52546
|
registerMemoryTools(server);
|
|
52543
52547
|
registerSchedulerTools(server);
|
|
@@ -53428,7 +53432,7 @@ var init_queen_tools = __esm({
|
|
|
53428
53432
|
type: "function",
|
|
53429
53433
|
function: {
|
|
53430
53434
|
name: "quoroom_web_search",
|
|
53431
|
-
description: "Search the web. Returns top 5 results.",
|
|
53435
|
+
description: "Search the web. Returns top 5 results. Queen should delegate this to workers first in control-plane mode.",
|
|
53432
53436
|
parameters: {
|
|
53433
53437
|
type: "object",
|
|
53434
53438
|
properties: {
|
|
@@ -53442,7 +53446,7 @@ var init_queen_tools = __esm({
|
|
|
53442
53446
|
type: "function",
|
|
53443
53447
|
function: {
|
|
53444
53448
|
name: "quoroom_web_fetch",
|
|
53445
|
-
description: "Fetch any URL and return its content as clean markdown.",
|
|
53449
|
+
description: "Fetch any URL and return its content as clean markdown. Queen should delegate this to workers first in control-plane mode.",
|
|
53446
53450
|
parameters: {
|
|
53447
53451
|
type: "object",
|
|
53448
53452
|
properties: {
|
|
@@ -53456,7 +53460,7 @@ var init_queen_tools = __esm({
|
|
|
53456
53460
|
type: "function",
|
|
53457
53461
|
function: {
|
|
53458
53462
|
name: "quoroom_browser",
|
|
53459
|
-
description: "Control a headless browser: navigate, click, fill forms, buy services, register domains, create accounts.",
|
|
53463
|
+
description: "Control a headless browser: navigate, click, fill forms, buy services, register domains, create accounts. Queen should delegate this to workers first in control-plane mode.",
|
|
53460
53464
|
parameters: {
|
|
53461
53465
|
type: "object",
|
|
53462
53466
|
properties: {
|
|
@@ -53617,6 +53621,32 @@ function msUntilQuietEnd(until) {
|
|
|
53617
53621
|
if (end <= now) end.setDate(end.getDate() + 1);
|
|
53618
53622
|
return end.getTime() - now.getTime();
|
|
53619
53623
|
}
|
|
53624
|
+
function nextAutoExecutorName(workers) {
|
|
53625
|
+
const names = new Set(workers.map((w) => w.name.toLowerCase()));
|
|
53626
|
+
let idx = 1;
|
|
53627
|
+
while (names.has(`executor-${idx}`)) idx++;
|
|
53628
|
+
return `executor-${idx}`;
|
|
53629
|
+
}
|
|
53630
|
+
function extractToolNameFromConsoleLog(content) {
|
|
53631
|
+
const usingMatch = content.match(/(?:Using|→)\s*([a-zA-Z0-9_]+)/);
|
|
53632
|
+
if (usingMatch?.[1]) return usingMatch[1];
|
|
53633
|
+
const callMatch = content.match(/^([a-zA-Z0-9_]+)\s*\(/);
|
|
53634
|
+
return callMatch?.[1] ?? null;
|
|
53635
|
+
}
|
|
53636
|
+
function resolveWorkerExecutionModel(db3, roomId, worker) {
|
|
53637
|
+
const explicit = worker.model?.trim();
|
|
53638
|
+
if (explicit) return explicit;
|
|
53639
|
+
const room = getRoom(db3, roomId);
|
|
53640
|
+
if (!room) return null;
|
|
53641
|
+
const roomModel = room.workerModel?.trim();
|
|
53642
|
+
if (!roomModel) return null;
|
|
53643
|
+
if (roomModel !== "queen") return roomModel;
|
|
53644
|
+
if (!room.queenWorkerId) return null;
|
|
53645
|
+
if (room.queenWorkerId === worker.id) return null;
|
|
53646
|
+
const queen = getWorker(db3, room.queenWorkerId);
|
|
53647
|
+
const queenModel = queen?.model?.trim();
|
|
53648
|
+
return queenModel || null;
|
|
53649
|
+
}
|
|
53620
53650
|
async function startAgentLoop(db3, roomId, workerId, options) {
|
|
53621
53651
|
const room = getRoom(db3, roomId);
|
|
53622
53652
|
if (!room) throw new Error(`Room ${roomId} not found`);
|
|
@@ -53732,12 +53762,29 @@ function pauseAgent(db3, workerId) {
|
|
|
53732
53762
|
}
|
|
53733
53763
|
updateAgentState(db3, workerId, "idle");
|
|
53734
53764
|
}
|
|
53765
|
+
function setRoomLaunchEnabled(roomId, enabled) {
|
|
53766
|
+
if (enabled) {
|
|
53767
|
+
launchedRoomIds.add(roomId);
|
|
53768
|
+
return;
|
|
53769
|
+
}
|
|
53770
|
+
launchedRoomIds.delete(roomId);
|
|
53771
|
+
}
|
|
53772
|
+
function isRoomLaunchEnabled(roomId) {
|
|
53773
|
+
return launchedRoomIds.has(roomId);
|
|
53774
|
+
}
|
|
53775
|
+
function clearRoomLaunchState() {
|
|
53776
|
+
launchedRoomIds.clear();
|
|
53777
|
+
}
|
|
53735
53778
|
function triggerAgent(db3, roomId, workerId, options) {
|
|
53736
53779
|
const loop = runningLoops.get(workerId);
|
|
53737
53780
|
if (loop?.running) {
|
|
53738
53781
|
if (loop.waitAbort) loop.waitAbort.abort();
|
|
53739
53782
|
return;
|
|
53740
53783
|
}
|
|
53784
|
+
const canColdStart = options?.allowColdStart === true || isRoomLaunchEnabled(roomId);
|
|
53785
|
+
if (!canColdStart) {
|
|
53786
|
+
return;
|
|
53787
|
+
}
|
|
53741
53788
|
startAgentLoop(db3, roomId, workerId, options).catch((err) => {
|
|
53742
53789
|
const msg = err instanceof Error ? err.message : String(err);
|
|
53743
53790
|
console.error(`Agent loop failed for worker ${workerId}: ${msg}`);
|
|
@@ -53785,7 +53832,7 @@ async function runCycle(db3, roomId, worker, maxTurns, options, abortSignal) {
|
|
|
53785
53832
|
void 0,
|
|
53786
53833
|
worker.id
|
|
53787
53834
|
);
|
|
53788
|
-
const model =
|
|
53835
|
+
const model = resolveWorkerExecutionModel(db3, roomId, worker);
|
|
53789
53836
|
const cycle = createWorkerCycle(db3, worker.id, roomId, model);
|
|
53790
53837
|
const logBuffer2 = createCycleLogBuffer(
|
|
53791
53838
|
cycle.id,
|
|
@@ -53794,6 +53841,23 @@ async function runCycle(db3, roomId, worker, maxTurns, options, abortSignal) {
|
|
|
53794
53841
|
);
|
|
53795
53842
|
options?.onCycleLifecycle?.("created", cycle.id, roomId);
|
|
53796
53843
|
try {
|
|
53844
|
+
if (!model) {
|
|
53845
|
+
const msg = "No model configured for this worker. Set an explicit worker model or room worker model.";
|
|
53846
|
+
logBuffer2.addSynthetic("error", msg);
|
|
53847
|
+
logBuffer2.flush();
|
|
53848
|
+
completeWorkerCycle(db3, cycle.id, msg, void 0);
|
|
53849
|
+
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
53850
|
+
logRoomActivity(
|
|
53851
|
+
db3,
|
|
53852
|
+
roomId,
|
|
53853
|
+
"error",
|
|
53854
|
+
`Agent cycle failed (${worker.name}): model is not configured`,
|
|
53855
|
+
msg,
|
|
53856
|
+
worker.id
|
|
53857
|
+
);
|
|
53858
|
+
updateAgentState(db3, worker.id, "idle");
|
|
53859
|
+
return msg;
|
|
53860
|
+
}
|
|
53797
53861
|
const provider = getModelProvider(model);
|
|
53798
53862
|
if (provider === "openai_api" || provider === "anthropic_api" || provider === "gemini_api") {
|
|
53799
53863
|
const apiKeyCheck = resolveApiKeyForModel(db3, roomId, model);
|
|
@@ -53820,10 +53884,44 @@ async function runCycle(db3, roomId, worker, maxTurns, options, abortSignal) {
|
|
|
53820
53884
|
status: g.status,
|
|
53821
53885
|
assignedWorkerId: g.assignedWorkerId
|
|
53822
53886
|
}));
|
|
53823
|
-
|
|
53887
|
+
let roomWorkers = listRoomWorkers(db3, roomId);
|
|
53888
|
+
const isQueen = worker.id === status2.room.queenWorkerId;
|
|
53824
53889
|
const unreadMessages = listRoomMessages(db3, roomId, "unread").slice(0, 5);
|
|
53890
|
+
if (isQueen) {
|
|
53891
|
+
const nonQueenWorkers = roomWorkers.filter((w) => w.id !== worker.id);
|
|
53892
|
+
if (nonQueenWorkers.length === 0) {
|
|
53893
|
+
const autoName = nextAutoExecutorName(roomWorkers);
|
|
53894
|
+
const executorPreset = WORKER_ROLE_PRESETS.executor;
|
|
53895
|
+
const inheritedModel = status2.room.workerModel === "queen" ? model : status2.room.workerModel?.trim();
|
|
53896
|
+
if (!inheritedModel) {
|
|
53897
|
+
const err = "Auto-create skipped: no worker model configured for executor.";
|
|
53898
|
+
logRoomActivity(db3, roomId, "error", err, "Set room worker model or queen model first.", worker.id);
|
|
53899
|
+
logBuffer2.addSynthetic("error", err);
|
|
53900
|
+
} else {
|
|
53901
|
+
createWorker(db3, {
|
|
53902
|
+
name: autoName,
|
|
53903
|
+
role: "executor",
|
|
53904
|
+
roomId,
|
|
53905
|
+
description: "Auto-created executor for queen-delegated execution work.",
|
|
53906
|
+
systemPrompt: "You are the room executor. Complete delegated tasks end-to-end, report concrete results, and save progress with quoroom_save_wip.",
|
|
53907
|
+
model: inheritedModel,
|
|
53908
|
+
cycleGapMs: executorPreset?.cycleGapMs,
|
|
53909
|
+
maxTurns: executorPreset?.maxTurns
|
|
53910
|
+
});
|
|
53911
|
+
logRoomActivity(
|
|
53912
|
+
db3,
|
|
53913
|
+
roomId,
|
|
53914
|
+
"system",
|
|
53915
|
+
`Auto-created worker "${autoName}" for delegation-first execution.`,
|
|
53916
|
+
"Model B (soft): queen coordinates, workers execute.",
|
|
53917
|
+
worker.id
|
|
53918
|
+
);
|
|
53919
|
+
logBuffer2.addSynthetic("system", `Auto-created worker "${autoName}" because queen had no executors.`);
|
|
53920
|
+
roomWorkers = listRoomWorkers(db3, roomId);
|
|
53921
|
+
}
|
|
53922
|
+
}
|
|
53923
|
+
}
|
|
53825
53924
|
const rolePreset = worker.role ? WORKER_ROLE_PRESETS[worker.role] : void 0;
|
|
53826
|
-
const isQueen = worker.id === status2.room.queenWorkerId;
|
|
53827
53925
|
const namePrefix = worker.name ? `Your name is ${worker.name}.
|
|
53828
53926
|
|
|
53829
53927
|
` : "";
|
|
@@ -53912,6 +54010,14 @@ At the end of this cycle, call quoroom_save_wip to save your updated position.`)
|
|
|
53912
54010
|
if (status2.room.goal) {
|
|
53913
54011
|
contextParts.push(`## Room Objective
|
|
53914
54012
|
${status2.room.goal}`);
|
|
54013
|
+
}
|
|
54014
|
+
if (isQueen) {
|
|
54015
|
+
contextParts.push(`## Queen Controller Contract (Model B)
|
|
54016
|
+
- You are the control plane: create workers, delegate tasks, and monitor delivery.
|
|
54017
|
+
- If there are no workers besides you, create one executor first.
|
|
54018
|
+
- Delegate all execution via quoroom_delegate_task and follow up with worker messages/pokes.
|
|
54019
|
+
- Keep governance active: use quoroom_announce for decisions and process objections/votes.
|
|
54020
|
+
- Do not perform execution tasks directly unless strictly unavoidable.`);
|
|
53915
54021
|
}
|
|
53916
54022
|
if (goalUpdates.length > 0) {
|
|
53917
54023
|
const workerMap = new Map(roomWorkers.map((w) => [w.id, w.name]));
|
|
@@ -54024,9 +54130,34 @@ ${unreadMessages.map(
|
|
|
54024
54130
|
const needsQueenTools = model === "openai" || model.startsWith("openai:") || model === "anthropic" || model.startsWith("anthropic:") || model.startsWith("claude-api:");
|
|
54025
54131
|
const roleToolDefs = isQueen ? QUEEN_TOOLS : WORKER_TOOLS;
|
|
54026
54132
|
const filteredToolDefs = allowSet ? roleToolDefs.filter((t) => allowSet.has(t.function.name)) : roleToolDefs;
|
|
54133
|
+
const queenExecutionToolsUsed = /* @__PURE__ */ new Set();
|
|
54134
|
+
const trackQueenExecutionTool = (toolName) => {
|
|
54135
|
+
if (!isQueen || !toolName) return;
|
|
54136
|
+
if (QUEEN_EXECUTION_TOOLS.has(toolName)) queenExecutionToolsUsed.add(toolName);
|
|
54137
|
+
};
|
|
54138
|
+
const persistQueenPolicyDeviation = () => {
|
|
54139
|
+
if (!isQueen || queenExecutionToolsUsed.size === 0) return;
|
|
54140
|
+
const used = [...queenExecutionToolsUsed].sort().join(", ");
|
|
54141
|
+
logRoomActivity(
|
|
54142
|
+
db3,
|
|
54143
|
+
roomId,
|
|
54144
|
+
"system",
|
|
54145
|
+
`Queen policy deviation: execution tool use detected (${used}).`,
|
|
54146
|
+
"Model B (soft): queen should delegate execution to workers and remain control-plane focused.",
|
|
54147
|
+
worker.id
|
|
54148
|
+
);
|
|
54149
|
+
const fresh = getWorker(db3, worker.id);
|
|
54150
|
+
const existing = fresh?.wip?.trim() ?? "";
|
|
54151
|
+
if (existing.includes(QUEEN_POLICY_WIP_HINT)) return;
|
|
54152
|
+
const nextWip = existing ? `${existing}
|
|
54153
|
+
|
|
54154
|
+
${QUEEN_POLICY_WIP_HINT}` : QUEEN_POLICY_WIP_HINT;
|
|
54155
|
+
updateWorkerWip(db3, worker.id, nextWip.slice(0, 2e3));
|
|
54156
|
+
};
|
|
54027
54157
|
const apiToolOpts = needsQueenTools ? {
|
|
54028
54158
|
toolDefs: filteredToolDefs,
|
|
54029
54159
|
onToolCall: async (toolName, args2) => {
|
|
54160
|
+
trackQueenExecutionTool(toolName);
|
|
54030
54161
|
logBuffer2.addSynthetic("tool_call", `\u2192 ${toolName}(${JSON.stringify(args2)})`);
|
|
54031
54162
|
const result2 = await executeQueenTool(db3, roomId, worker.id, toolName, args2);
|
|
54032
54163
|
logBuffer2.addSynthetic("tool_result", result2.content);
|
|
@@ -54040,7 +54171,12 @@ ${unreadMessages.map(
|
|
|
54040
54171
|
apiKey,
|
|
54041
54172
|
timeoutMs: worker.role === "executor" ? 30 * 60 * 1e3 : 15 * 60 * 1e3,
|
|
54042
54173
|
maxTurns: maxTurns ?? 50,
|
|
54043
|
-
onConsoleLog:
|
|
54174
|
+
onConsoleLog: (entry) => {
|
|
54175
|
+
if (entry.entryType === "tool_call") {
|
|
54176
|
+
trackQueenExecutionTool(extractToolNameFromConsoleLog(entry.content));
|
|
54177
|
+
}
|
|
54178
|
+
logBuffer2.onConsoleLog(entry);
|
|
54179
|
+
},
|
|
54044
54180
|
// CLI models: block non-quoroom MCP tools (daymon, etc.)
|
|
54045
54181
|
disallowedTools: isCli ? "mcp__daymon*" : void 0,
|
|
54046
54182
|
// CLI models: bypass permission prompts for headless operation
|
|
@@ -54073,6 +54209,7 @@ ${unreadMessages.map(
|
|
|
54073
54209
|
completeWorkerCycle(db3, cycle.id, canceledMessage, result.usage);
|
|
54074
54210
|
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
54075
54211
|
updateAgentState(db3, worker.id, "idle");
|
|
54212
|
+
persistQueenPolicyDeviation();
|
|
54076
54213
|
return result.output;
|
|
54077
54214
|
}
|
|
54078
54215
|
const rateLimitInfo = checkRateLimit(result);
|
|
@@ -54101,6 +54238,7 @@ ${unreadMessages.map(
|
|
|
54101
54238
|
logBuffer2.flush();
|
|
54102
54239
|
}
|
|
54103
54240
|
}
|
|
54241
|
+
persistQueenPolicyDeviation();
|
|
54104
54242
|
return result.output;
|
|
54105
54243
|
}
|
|
54106
54244
|
if (isCli && result.sessionId) {
|
|
@@ -54109,6 +54247,7 @@ ${unreadMessages.map(
|
|
|
54109
54247
|
if (result.output && model !== "claude" && !model.startsWith("codex")) {
|
|
54110
54248
|
logBuffer2.addSynthetic("assistant_text", result.output);
|
|
54111
54249
|
}
|
|
54250
|
+
persistQueenPolicyDeviation();
|
|
54112
54251
|
logBuffer2.addSynthetic("system", "Cycle completed");
|
|
54113
54252
|
if (result.usage && (result.usage.inputTokens > 0 || result.usage.outputTokens > 0)) {
|
|
54114
54253
|
logBuffer2.addSynthetic("system", `Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`);
|
|
@@ -54159,8 +54298,9 @@ function _stopAllLoops() {
|
|
|
54159
54298
|
if (loop.cycleAbort) loop.cycleAbort.abort();
|
|
54160
54299
|
}
|
|
54161
54300
|
runningLoops.clear();
|
|
54301
|
+
clearRoomLaunchState();
|
|
54162
54302
|
}
|
|
54163
|
-
var runningLoops, RateLimitError;
|
|
54303
|
+
var QUEEN_EXECUTION_TOOLS, QUEEN_POLICY_WIP_HINT, runningLoops, launchedRoomIds, RateLimitError;
|
|
54164
54304
|
var init_agent_loop = __esm({
|
|
54165
54305
|
"src/shared/agent-loop.ts"() {
|
|
54166
54306
|
"use strict";
|
|
@@ -54173,7 +54313,14 @@ var init_agent_loop = __esm({
|
|
|
54173
54313
|
init_console_log_buffer();
|
|
54174
54314
|
init_queen_tools();
|
|
54175
54315
|
init_constants();
|
|
54316
|
+
QUEEN_EXECUTION_TOOLS = /* @__PURE__ */ new Set([
|
|
54317
|
+
"quoroom_web_search",
|
|
54318
|
+
"quoroom_web_fetch",
|
|
54319
|
+
"quoroom_browser"
|
|
54320
|
+
]);
|
|
54321
|
+
QUEEN_POLICY_WIP_HINT = "[policy] Queen control-plane mode: delegate execution tasks to workers with quoroom_delegate_task, then monitor, unblock, and report outcomes. Avoid direct web/browser execution.";
|
|
54176
54322
|
runningLoops = /* @__PURE__ */ new Map();
|
|
54323
|
+
launchedRoomIds = /* @__PURE__ */ new Set();
|
|
54177
54324
|
RateLimitError = class extends Error {
|
|
54178
54325
|
constructor(info) {
|
|
54179
54326
|
super(`Rate limited: wait ${Math.round(info.waitMs / 1e3)}s`);
|
|
@@ -54189,7 +54336,7 @@ var require_package = __commonJS({
|
|
|
54189
54336
|
"package.json"(exports2, module2) {
|
|
54190
54337
|
module2.exports = {
|
|
54191
54338
|
name: "quoroom",
|
|
54192
|
-
version: "0.1.
|
|
54339
|
+
version: "0.1.39",
|
|
54193
54340
|
description: "Open-source local AI agent framework \u2014 Queen, Workers, Quorum. Experimental research tool.",
|
|
54194
54341
|
main: "./out/mcp/server.js",
|
|
54195
54342
|
bin: {
|
|
@@ -54219,16 +54366,20 @@ var require_package = __commonJS({
|
|
|
54219
54366
|
"kill:quoroom-runtime": "node scripts/kill-quoroom-runtime.js",
|
|
54220
54367
|
"kill:dev-runtime": "npm run kill:dev-ports && npm run kill:quoroom-runtime",
|
|
54221
54368
|
"dev:links": "node scripts/dev-links.js",
|
|
54222
|
-
dev:
|
|
54369
|
+
dev: "node scripts/run-dev.js",
|
|
54223
54370
|
"dev:win": "node scripts/run-dev.js",
|
|
54371
|
+
"dev:with-cloud": "node scripts/run-dev.js --with-cloud",
|
|
54372
|
+
"dev:with-cloud:win": "node scripts/run-dev.js --with-cloud",
|
|
54224
54373
|
"dev:room": "sh -c 'npm run kill:dev-runtime && export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
54225
54374
|
"dev:room:win": "node scripts/dev-room.js --port 4700",
|
|
54226
54375
|
"dev:room:isolated:win": "node scripts/dev-room.js --isolated --port 4700",
|
|
54227
54376
|
"dev:room:isolated": "sh -c 'npm run kill:dev-runtime && export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
54228
54377
|
"dev:room:shared": "npm run kill:dev-runtime && npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
54229
54378
|
"doctor:split": "node scripts/doctor-split.js",
|
|
54230
|
-
"dev:isolated":
|
|
54379
|
+
"dev:isolated": "node scripts/run-dev.js --isolated",
|
|
54231
54380
|
"dev:isolated:win": "node scripts/run-dev.js --isolated",
|
|
54381
|
+
"dev:isolated:with-cloud": "node scripts/run-dev.js --isolated --with-cloud",
|
|
54382
|
+
"dev:isolated:with-cloud:win": "node scripts/run-dev.js --isolated --with-cloud",
|
|
54232
54383
|
"dev:cloud": `sh -c 'npm run kill:ports -- 3715 && cd ../cloud && PORT=3715 CLOUD_PUBLIC_URL=http://127.0.0.1:3715 CLOUD_ALLOWED_ORIGINS='"'"'http://127.0.0.1:3715,http://localhost:3715,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'"'"' npm start'`,
|
|
54233
54384
|
"dev:cloud:win": "node scripts/dev-cloud.js",
|
|
54234
54385
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
@@ -55681,13 +55832,10 @@ async function executeClerkTool(db3, toolName, args2, ctx) {
|
|
|
55681
55832
|
return { content: `Deleted room "${room.name}" (#${room.id}).` };
|
|
55682
55833
|
}
|
|
55683
55834
|
case "quoroom_start_queen": {
|
|
55684
|
-
|
|
55685
|
-
|
|
55686
|
-
|
|
55687
|
-
|
|
55688
|
-
stopRoomRuntime(db3, room.id, "Runtime reset before queen start");
|
|
55689
|
-
triggerAgent(db3, room.id, room.queenWorkerId);
|
|
55690
|
-
return { content: `Started queen in "${room.name}" (#${room.id}).` };
|
|
55835
|
+
return {
|
|
55836
|
+
content: "Error: direct queen start is disabled. Start the room manually from the Room controls.",
|
|
55837
|
+
isError: true
|
|
55838
|
+
};
|
|
55691
55839
|
}
|
|
55692
55840
|
case "quoroom_stop_queen": {
|
|
55693
55841
|
const room = resolveRoom(db3, args2);
|
|
@@ -58256,6 +58404,9 @@ async function syncCloudRoomMessages(db3) {
|
|
|
58256
58404
|
}
|
|
58257
58405
|
function startServerRuntime(db3) {
|
|
58258
58406
|
stopServerRuntime();
|
|
58407
|
+
clearRoomLaunchState();
|
|
58408
|
+
const cleanedCycles = cleanupStaleCycles(db3);
|
|
58409
|
+
if (cleanedCycles > 0) console.log(`Cleaned up ${cleanedCycles} stale worker cycles`);
|
|
58259
58410
|
ensureClerkContactCheckTasks(db3);
|
|
58260
58411
|
refreshCronJobs(db3);
|
|
58261
58412
|
runDueOneTimeTasks(db3);
|
|
@@ -58283,30 +58434,8 @@ function startServerRuntime(db3) {
|
|
|
58283
58434
|
clerkAlertTimer = setInterval(() => {
|
|
58284
58435
|
queueClerkAlertRelay(db3);
|
|
58285
58436
|
}, CLERK_ALERT_RELAY_MS);
|
|
58286
|
-
resumeActiveQueens(db3);
|
|
58287
58437
|
startCommentaryEngine(db3);
|
|
58288
58438
|
}
|
|
58289
|
-
function makeCycleCallbacks() {
|
|
58290
|
-
return {
|
|
58291
|
-
onCycleLogEntry: (entry) => {
|
|
58292
|
-
eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry);
|
|
58293
|
-
},
|
|
58294
|
-
onCycleLifecycle: (event, cycleId, roomId) => {
|
|
58295
|
-
eventBus.emit(`room:${roomId}`, `cycle:${event}`, { cycleId, roomId });
|
|
58296
|
-
}
|
|
58297
|
-
};
|
|
58298
|
-
}
|
|
58299
|
-
function resumeActiveQueens(db3) {
|
|
58300
|
-
const cleaned = cleanupStaleCycles(db3);
|
|
58301
|
-
if (cleaned > 0) console.log(`Cleaned up ${cleaned} stale worker cycles`);
|
|
58302
|
-
const rooms = listRooms(db3, "active");
|
|
58303
|
-
const callbacks = makeCycleCallbacks();
|
|
58304
|
-
for (const room of rooms) {
|
|
58305
|
-
if (!room.queenWorkerId) continue;
|
|
58306
|
-
console.log(`Auto-resuming queen for room "${room.name}" (#${room.id})`);
|
|
58307
|
-
triggerAgent(db3, room.id, room.queenWorkerId, callbacks);
|
|
58308
|
-
}
|
|
58309
|
-
}
|
|
58310
58439
|
function stopServerRuntime() {
|
|
58311
58440
|
stopCommentaryEngine();
|
|
58312
58441
|
if (schedulerTimer) clearInterval(schedulerTimer);
|
|
@@ -58387,6 +58516,16 @@ function emitQueenState(roomId, running) {
|
|
|
58387
58516
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
58388
58517
|
});
|
|
58389
58518
|
}
|
|
58519
|
+
function makeCycleCallbacks(roomId) {
|
|
58520
|
+
return {
|
|
58521
|
+
onCycleLogEntry: (entry) => {
|
|
58522
|
+
eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry);
|
|
58523
|
+
},
|
|
58524
|
+
onCycleLifecycle: (event, cycleId, _roomId) => {
|
|
58525
|
+
eventBus.emit(`room:${roomId}`, `cycle:${event}`, { cycleId, roomId });
|
|
58526
|
+
}
|
|
58527
|
+
};
|
|
58528
|
+
}
|
|
58390
58529
|
function getLocalReferredRooms(db3, roomId) {
|
|
58391
58530
|
const keeperCode = (getSetting(db3, "keeper_referral_code") ?? "").trim();
|
|
58392
58531
|
if (!keeperCode) return [];
|
|
@@ -58600,11 +58739,8 @@ function registerRoomRoutes(router) {
|
|
|
58600
58739
|
}
|
|
58601
58740
|
}
|
|
58602
58741
|
if (updates.status === "stopped") {
|
|
58603
|
-
|
|
58604
|
-
|
|
58605
|
-
updateAgentState(ctx.db, w.id, "idle");
|
|
58606
|
-
pauseAgent(ctx.db, w.id);
|
|
58607
|
-
}
|
|
58742
|
+
setRoomLaunchEnabled(roomId, false);
|
|
58743
|
+
stopRoomRuntime(ctx.db, roomId, "Room archived");
|
|
58608
58744
|
logRoomActivity(ctx.db, roomId, "system", "Room archived");
|
|
58609
58745
|
}
|
|
58610
58746
|
const updated = getRoom(ctx.db, roomId);
|
|
@@ -58619,21 +58755,59 @@ function registerRoomRoutes(router) {
|
|
|
58619
58755
|
const roomId = Number(ctx.params.id);
|
|
58620
58756
|
try {
|
|
58621
58757
|
pauseRoom(ctx.db, roomId);
|
|
58622
|
-
|
|
58623
|
-
|
|
58624
|
-
pauseAgent(ctx.db, w.id);
|
|
58625
|
-
}
|
|
58758
|
+
setRoomLaunchEnabled(roomId, false);
|
|
58759
|
+
stopRoomRuntime(ctx.db, roomId, "Room stopped by keeper");
|
|
58626
58760
|
eventBus.emit(`room:${roomId}`, "room:paused", { roomId });
|
|
58761
|
+
eventBus.emit(`room:${roomId}`, "room:queen_stopped", { roomId, running: false });
|
|
58762
|
+
emitQueenState(roomId, false);
|
|
58627
58763
|
emitRoomsUpdated("room_paused", { roomId });
|
|
58628
58764
|
return { data: { ok: true } };
|
|
58629
58765
|
} catch (e) {
|
|
58630
58766
|
return { status: 404, error: e.message };
|
|
58631
58767
|
}
|
|
58632
58768
|
});
|
|
58769
|
+
router.post("/api/rooms/:id/start", (ctx) => {
|
|
58770
|
+
const roomId = Number(ctx.params.id);
|
|
58771
|
+
const room = getRoom(ctx.db, roomId);
|
|
58772
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
58773
|
+
if (room.status === "stopped") return { status: 400, error: "Room is stopped" };
|
|
58774
|
+
if (!room.queenWorkerId) return { status: 400, error: "No queen worker" };
|
|
58775
|
+
if (room.status !== "active") {
|
|
58776
|
+
updateRoom(ctx.db, roomId, { status: "active" });
|
|
58777
|
+
}
|
|
58778
|
+
setRoomLaunchEnabled(roomId, true);
|
|
58779
|
+
stopRoomRuntime(ctx.db, roomId, "Runtime reset before room start");
|
|
58780
|
+
triggerAgent(ctx.db, roomId, room.queenWorkerId, {
|
|
58781
|
+
...makeCycleCallbacks(roomId),
|
|
58782
|
+
allowColdStart: true
|
|
58783
|
+
});
|
|
58784
|
+
eventBus.emit(`room:${roomId}`, "room:started", { roomId });
|
|
58785
|
+
eventBus.emit(`room:${roomId}`, "room:queen_started", { roomId, running: true });
|
|
58786
|
+
emitQueenState(roomId, true);
|
|
58787
|
+
emitRoomsUpdated("room_started", { roomId });
|
|
58788
|
+
return { data: { ok: true, running: true } };
|
|
58789
|
+
});
|
|
58790
|
+
router.post("/api/rooms/:id/stop", (ctx) => {
|
|
58791
|
+
const roomId = Number(ctx.params.id);
|
|
58792
|
+
const room = getRoom(ctx.db, roomId);
|
|
58793
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
58794
|
+
setRoomLaunchEnabled(roomId, false);
|
|
58795
|
+
stopRoomRuntime(ctx.db, roomId, "Room stopped by keeper");
|
|
58796
|
+
if (room.status !== "stopped") {
|
|
58797
|
+
updateRoom(ctx.db, roomId, { status: "paused" });
|
|
58798
|
+
}
|
|
58799
|
+
eventBus.emit(`room:${roomId}`, "room:stopped", { roomId });
|
|
58800
|
+
eventBus.emit(`room:${roomId}`, "room:paused", { roomId });
|
|
58801
|
+
eventBus.emit(`room:${roomId}`, "room:queen_stopped", { roomId, running: false });
|
|
58802
|
+
emitQueenState(roomId, false);
|
|
58803
|
+
emitRoomsUpdated("room_paused", { roomId });
|
|
58804
|
+
return { data: { ok: true, running: false } };
|
|
58805
|
+
});
|
|
58633
58806
|
router.post("/api/rooms/:id/restart", (ctx) => {
|
|
58634
58807
|
const roomId = Number(ctx.params.id);
|
|
58635
58808
|
const { goal } = ctx.body || {};
|
|
58636
58809
|
try {
|
|
58810
|
+
setRoomLaunchEnabled(roomId, false);
|
|
58637
58811
|
restartRoom(ctx.db, roomId, goal);
|
|
58638
58812
|
eventBus.emit(`room:${roomId}`, "room:restarted", { roomId });
|
|
58639
58813
|
emitRoomsUpdated("room_restarted", { roomId });
|
|
@@ -58661,30 +58835,11 @@ function registerRoomRoutes(router) {
|
|
|
58661
58835
|
}
|
|
58662
58836
|
};
|
|
58663
58837
|
});
|
|
58664
|
-
router.post("/api/rooms/:id/queen/start", (
|
|
58665
|
-
|
|
58666
|
-
const room = getRoom(ctx.db, roomId);
|
|
58667
|
-
if (!room) return { status: 404, error: "Room not found" };
|
|
58668
|
-
if (room.status !== "active") return { status: 400, error: "Room is not active" };
|
|
58669
|
-
if (!room.queenWorkerId) return { status: 400, error: "No queen worker" };
|
|
58670
|
-
stopRoomRuntime(ctx.db, roomId, "Runtime reset before queen start");
|
|
58671
|
-
triggerAgent(ctx.db, roomId, room.queenWorkerId, {
|
|
58672
|
-
onCycleLogEntry: (entry) => eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry),
|
|
58673
|
-
onCycleLifecycle: (event, cycleId) => eventBus.emit(`room:${roomId}`, `cycle:${event}`, { cycleId, roomId })
|
|
58674
|
-
});
|
|
58675
|
-
eventBus.emit(`room:${roomId}`, "room:queen_started", { roomId, running: true });
|
|
58676
|
-
emitQueenState(roomId, true);
|
|
58677
|
-
return { data: { ok: true, running: true } };
|
|
58838
|
+
router.post("/api/rooms/:id/queen/start", (_ctx) => {
|
|
58839
|
+
return { status: 410, error: "Deprecated. Use POST /api/rooms/:id/start" };
|
|
58678
58840
|
});
|
|
58679
|
-
router.post("/api/rooms/:id/queen/stop", (
|
|
58680
|
-
|
|
58681
|
-
const room = getRoom(ctx.db, roomId);
|
|
58682
|
-
if (!room) return { status: 404, error: "Room not found" };
|
|
58683
|
-
if (!room.queenWorkerId) return { status: 400, error: "No queen worker" };
|
|
58684
|
-
stopRoomRuntime(ctx.db, roomId, "Queen stopped by keeper");
|
|
58685
|
-
eventBus.emit(`room:${roomId}`, "room:queen_stopped", { roomId, running: false });
|
|
58686
|
-
emitQueenState(roomId, false);
|
|
58687
|
-
return { data: { ok: true, running: false } };
|
|
58841
|
+
router.post("/api/rooms/:id/queen/stop", (_ctx) => {
|
|
58842
|
+
return { status: 410, error: "Deprecated. Use POST /api/rooms/:id/stop" };
|
|
58688
58843
|
});
|
|
58689
58844
|
router.get("/api/rooms/:id/cycles", (ctx) => {
|
|
58690
58845
|
const roomId = Number(ctx.params.id);
|
|
@@ -58698,8 +58853,8 @@ function registerRoomRoutes(router) {
|
|
|
58698
58853
|
const today = getRoomTokenUsageToday(ctx.db, roomId);
|
|
58699
58854
|
const room = getRoom(ctx.db, roomId);
|
|
58700
58855
|
const queenWorker = room?.queenWorkerId ? getWorker(ctx.db, room.queenWorkerId) : null;
|
|
58701
|
-
const model = queenWorker?.model ?? room?.workerModel ??
|
|
58702
|
-
const isApiModel = model.startsWith("openai") || model.startsWith("anthropic") || model.startsWith("claude-api");
|
|
58856
|
+
const model = queenWorker?.model ?? room?.workerModel ?? null;
|
|
58857
|
+
const isApiModel = !!model && (model.startsWith("openai") || model.startsWith("anthropic") || model.startsWith("claude-api"));
|
|
58703
58858
|
return { data: { total, today, isApiModel } };
|
|
58704
58859
|
});
|
|
58705
58860
|
router.get("/api/cycles/:id/logs", (ctx) => {
|
|
@@ -58712,10 +58867,8 @@ function registerRoomRoutes(router) {
|
|
|
58712
58867
|
router.delete("/api/rooms/:id", (ctx) => {
|
|
58713
58868
|
const roomId = Number(ctx.params.id);
|
|
58714
58869
|
try {
|
|
58715
|
-
|
|
58716
|
-
|
|
58717
|
-
pauseAgent(ctx.db, w.id);
|
|
58718
|
-
}
|
|
58870
|
+
setRoomLaunchEnabled(roomId, false);
|
|
58871
|
+
stopRoomRuntime(ctx.db, roomId, "Room deleted");
|
|
58719
58872
|
deleteRoom2(ctx.db, roomId);
|
|
58720
58873
|
eventBus.emit(`room:${roomId}`, "room:deleted", { roomId });
|
|
58721
58874
|
emitRoomsUpdated("room_deleted", { roomId });
|
|
@@ -58795,6 +58948,9 @@ function registerWorkerRoutes(router) {
|
|
|
58795
58948
|
const room = getRoom(ctx.db, worker.roomId);
|
|
58796
58949
|
if (!room) return { status: 404, error: "Room not found" };
|
|
58797
58950
|
if (room.status !== "active") return { status: 400, error: "Room is not active" };
|
|
58951
|
+
if (!isRoomLaunchEnabled(worker.roomId)) {
|
|
58952
|
+
return { status: 409, error: "Room runtime is not started. Start the room first." };
|
|
58953
|
+
}
|
|
58798
58954
|
triggerAgent(ctx.db, worker.roomId, id, {
|
|
58799
58955
|
onCycleLogEntry: (entry) => eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry),
|
|
58800
58956
|
onCycleLifecycle: (event, cycleId) => eventBus.emit(`room:${worker.roomId}`, `cycle:${event}`, { cycleId, roomId: worker.roomId })
|
|
@@ -59755,7 +59911,7 @@ function semverGt(a, b) {
|
|
|
59755
59911
|
}
|
|
59756
59912
|
function getCurrentVersion() {
|
|
59757
59913
|
try {
|
|
59758
|
-
return true ? "0.1.
|
|
59914
|
+
return true ? "0.1.39" : null.version;
|
|
59759
59915
|
} catch {
|
|
59760
59916
|
return "0.0.0";
|
|
59761
59917
|
}
|
|
@@ -59962,7 +60118,7 @@ var init_updateChecker = __esm({
|
|
|
59962
60118
|
function getVersion3() {
|
|
59963
60119
|
if (cachedVersion) return cachedVersion;
|
|
59964
60120
|
try {
|
|
59965
|
-
cachedVersion = true ? "0.1.
|
|
60121
|
+
cachedVersion = true ? "0.1.39" : null.version;
|
|
59966
60122
|
} catch {
|
|
59967
60123
|
cachedVersion = "unknown";
|
|
59968
60124
|
}
|
|
@@ -66228,7 +66384,7 @@ __export(update_exports, {
|
|
|
66228
66384
|
});
|
|
66229
66385
|
function getCurrentVersion2() {
|
|
66230
66386
|
try {
|
|
66231
|
-
return true ? "0.1.
|
|
66387
|
+
return true ? "0.1.39" : null.version;
|
|
66232
66388
|
} catch {
|
|
66233
66389
|
return "0.0.0";
|
|
66234
66390
|
}
|
package/out/mcp/server.js
CHANGED
|
@@ -46264,13 +46264,14 @@ Every cycle:
|
|
|
46264
46264
|
1. Check if workers reported results (messages, completed goals)
|
|
46265
46265
|
2. If work is done \u2192 send results to keeper, take next step
|
|
46266
46266
|
3. If work is stuck \u2192 help unblock (new instructions, different approach)
|
|
46267
|
-
4. If
|
|
46268
|
-
5. If
|
|
46267
|
+
4. If no workers exist yet \u2192 create an executor worker first
|
|
46268
|
+
5. If new work is needed \u2192 delegate to a worker with clear instructions, then poke/follow up
|
|
46269
|
+
6. If a decision needs input \u2192 announce it and process objections/votes (announce/object flow)
|
|
46269
46270
|
|
|
46270
46271
|
Talk to the keeper regularly \u2014 they are your client.
|
|
46271
46272
|
|
|
46272
|
-
Do NOT
|
|
46273
|
-
|
|
46273
|
+
Do NOT execute tasks directly (research, form filling, account creation, browser automation).
|
|
46274
|
+
Stay control-plane only: create workers, delegate, monitor, unblock, report.`;
|
|
46274
46275
|
function createRoom2(db2, input) {
|
|
46275
46276
|
const config2 = { ...DEFAULT_ROOM_CONFIG, ...input.config };
|
|
46276
46277
|
const room = createRoom(db2, input.name, input.goal, config2, input.referredByCode);
|
|
@@ -49085,7 +49086,7 @@ init_db();
|
|
|
49085
49086
|
async function main() {
|
|
49086
49087
|
const server = new McpServer({
|
|
49087
49088
|
name: "quoroom",
|
|
49088
|
-
version: true ? "0.1.
|
|
49089
|
+
version: true ? "0.1.39" : "0.0.0"
|
|
49089
49090
|
});
|
|
49090
49091
|
registerMemoryTools(server);
|
|
49091
49092
|
registerSchedulerTools(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quoroom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"description": "Open-source local AI agent framework — Queen, Workers, Quorum. Experimental research tool.",
|
|
5
5
|
"main": "./out/mcp/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,16 +30,20 @@
|
|
|
30
30
|
"kill:quoroom-runtime": "node scripts/kill-quoroom-runtime.js",
|
|
31
31
|
"kill:dev-runtime": "npm run kill:dev-ports && npm run kill:quoroom-runtime",
|
|
32
32
|
"dev:links": "node scripts/dev-links.js",
|
|
33
|
-
"dev": "
|
|
33
|
+
"dev": "node scripts/run-dev.js",
|
|
34
34
|
"dev:win": "node scripts/run-dev.js",
|
|
35
|
+
"dev:with-cloud": "node scripts/run-dev.js --with-cloud",
|
|
36
|
+
"dev:with-cloud:win": "node scripts/run-dev.js --with-cloud",
|
|
35
37
|
"dev:room": "sh -c 'npm run kill:dev-runtime && export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
36
38
|
"dev:room:win": "node scripts/dev-room.js --port 4700",
|
|
37
39
|
"dev:room:isolated:win": "node scripts/dev-room.js --isolated --port 4700",
|
|
38
40
|
"dev:room:isolated": "sh -c 'npm run kill:dev-runtime && export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
39
41
|
"dev:room:shared": "npm run kill:dev-runtime && npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
40
42
|
"doctor:split": "node scripts/doctor-split.js",
|
|
41
|
-
"dev:isolated": "
|
|
43
|
+
"dev:isolated": "node scripts/run-dev.js --isolated",
|
|
42
44
|
"dev:isolated:win": "node scripts/run-dev.js --isolated",
|
|
45
|
+
"dev:isolated:with-cloud": "node scripts/run-dev.js --isolated --with-cloud",
|
|
46
|
+
"dev:isolated:with-cloud:win": "node scripts/run-dev.js --isolated --with-cloud",
|
|
43
47
|
"dev:cloud": "sh -c 'npm run kill:ports -- 3715 && cd ../cloud && PORT=3715 CLOUD_PUBLIC_URL=http://127.0.0.1:3715 CLOUD_ALLOWED_ORIGINS='\"'\"'http://127.0.0.1:3715,http://localhost:3715,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'\"'\"' npm start'",
|
|
44
48
|
"dev:cloud:win": "node scripts/dev-cloud.js",
|
|
45
49
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|