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 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
 
@@ -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.37",
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: `sh -c 'npm run kill:dev-runtime && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
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": `sh -c 'npm run kill:dev-runtime && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'`,
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 new work needed \u2192 delegate to a worker with clear instructions
22623
- 5. If a decision needs input \u2192 announce it (workers can object within 10 min)
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 do execution work yourself (research, form filling, account creation).
22628
- Delegate it. That's what workers are for.`;
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
- return executeClaude(options);
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 = worker.model ?? "claude";
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
- const roomWorkers = listRoomWorkers(db2, roomId);
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: logBuffer2.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
- const room = resolveRoom(db2, args);
28281
- if (!room) return { content: "Error: room not found.", isError: true };
28282
- if (!room.queenWorkerId) return { content: `Error: room "${room.name}" has no queen worker.`, isError: true };
28283
- if (room.status !== "active") return { content: `Error: room "${room.name}" is not active.`, isError: true };
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
- const workers = listRoomWorkers(ctx.db, roomId);
30796
- for (const w of workers) {
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
- const workers = listRoomWorkers(ctx.db, roomId);
30815
- for (const w of workers) {
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", (ctx) => {
30857
- const roomId = Number(ctx.params.id);
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", (ctx) => {
30872
- const roomId = Number(ctx.params.id);
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 ?? "claude";
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
- const workers = listRoomWorkers(ctx.db, roomId);
30908
- for (const w of workers) {
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.37" : null.version;
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.37" : null.version;
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
- return executeClaude(options);
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 new work needed \u2192 delegate to a worker with clear instructions
49458
- 5. If a decision needs input \u2192 announce it (workers can object within 10 min)
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 do execution work yourself (research, form filling, account creation).
49463
- Delegate it. That's what workers are for.`;
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.37" : "0.0.0"
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 = worker.model ?? "claude";
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
- const roomWorkers = listRoomWorkers(db3, roomId);
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: logBuffer2.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.37",
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: `sh -c 'npm run kill:dev-runtime && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
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": `sh -c 'npm run kill:dev-runtime && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'`,
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
- const room = resolveRoom(db3, args2);
55685
- if (!room) return { content: "Error: room not found.", isError: true };
55686
- if (!room.queenWorkerId) return { content: `Error: room "${room.name}" has no queen worker.`, isError: true };
55687
- if (room.status !== "active") return { content: `Error: room "${room.name}" is not active.`, isError: true };
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
- const workers = listRoomWorkers(ctx.db, roomId);
58604
- for (const w of workers) {
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
- const workers = listRoomWorkers(ctx.db, roomId);
58623
- for (const w of workers) {
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", (ctx) => {
58665
- const roomId = Number(ctx.params.id);
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", (ctx) => {
58680
- const roomId = Number(ctx.params.id);
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 ?? "claude";
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
- const workers = listRoomWorkers(ctx.db, roomId);
58716
- for (const w of workers) {
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.37" : null.version;
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.37" : null.version;
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.37" : null.version;
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 new work needed \u2192 delegate to a worker with clear instructions
46268
- 5. If a decision needs input \u2192 announce it (workers can object within 10 min)
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 do execution work yourself (research, form filling, account creation).
46273
- Delegate it. That's what workers are for.`;
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.37" : "0.0.0"
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.37",
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": "sh -c 'npm run kill:dev-runtime && trap \"kill 0\" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'",
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": "sh -c 'npm run kill:dev-runtime && trap \"kill 0\" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'",
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",