quoroom 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/mcp/api-server.js +320 -39
- package/out/mcp/cli.js +322 -41
- package/out/mcp/server.js +11 -1
- package/package.json +3 -3
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.22",
|
|
9918
9918
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
9919
9919
|
main: "./out/mcp/server.js",
|
|
9920
9920
|
bin: {
|
|
@@ -9939,7 +9939,7 @@ var require_package = __commonJS({
|
|
|
9939
9939
|
"build:mcp": "node scripts/build-mcp.js",
|
|
9940
9940
|
"build:ui": "vite build --config src/ui/vite.config.ts",
|
|
9941
9941
|
"kill:ports": "node scripts/kill-ports.js",
|
|
9942
|
-
"kill:dev-ports": "npm run kill:ports -- 4700
|
|
9942
|
+
"kill:dev-ports": "npm run kill:ports -- 4700 3715 5173",
|
|
9943
9943
|
"dev:links": "node scripts/dev-links.js",
|
|
9944
9944
|
dev: `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
|
|
9945
9945
|
"dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
@@ -9947,7 +9947,7 @@ var require_package = __commonJS({
|
|
|
9947
9947
|
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
9948
9948
|
"doctor:split": "node scripts/doctor-split.js",
|
|
9949
9949
|
"dev:isolated": `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'`,
|
|
9950
|
-
"dev:cloud": `sh -c 'npm run kill:ports --
|
|
9950
|
+
"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'`,
|
|
9951
9951
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
9952
9952
|
"seed:style-demo": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev; node scripts/seed-style-demo.js'",
|
|
9953
9953
|
typecheck: "tsc --noEmit",
|
|
@@ -12069,6 +12069,7 @@ function mapRoomRow(row) {
|
|
|
12069
12069
|
queenNickname: row.queen_nickname ?? null,
|
|
12070
12070
|
chatSessionId: row.chat_session_id ?? null,
|
|
12071
12071
|
referredByCode: row.referred_by_code ?? null,
|
|
12072
|
+
allowedTools: row.allowed_tools ?? null,
|
|
12072
12073
|
webhookToken: row.webhook_token ?? null,
|
|
12073
12074
|
createdAt: row.created_at,
|
|
12074
12075
|
updatedAt: row.updated_at
|
|
@@ -12161,6 +12162,7 @@ function updateRoom(db2, id, updates) {
|
|
|
12161
12162
|
config: "config",
|
|
12162
12163
|
referredByCode: "referred_by_code",
|
|
12163
12164
|
queenNickname: "queen_nickname",
|
|
12165
|
+
allowedTools: "allowed_tools",
|
|
12164
12166
|
webhookToken: "webhook_token"
|
|
12165
12167
|
};
|
|
12166
12168
|
const fields = [];
|
|
@@ -12856,6 +12858,22 @@ function listRoomCycles(db2, roomId, limit = 20) {
|
|
|
12856
12858
|
).all(roomId, safeLimit);
|
|
12857
12859
|
return rows.map(mapWorkerCycleRow);
|
|
12858
12860
|
}
|
|
12861
|
+
function countProductiveToolCalls(db2, workerId, lastNCycles = 2) {
|
|
12862
|
+
const row = db2.prepare(`
|
|
12863
|
+
SELECT COUNT(*) as cnt FROM cycle_logs
|
|
12864
|
+
WHERE cycle_id IN (
|
|
12865
|
+
SELECT id FROM worker_cycles
|
|
12866
|
+
WHERE worker_id = ? AND status = 'completed'
|
|
12867
|
+
ORDER BY started_at DESC LIMIT ?
|
|
12868
|
+
)
|
|
12869
|
+
AND entry_type = 'tool_call'
|
|
12870
|
+
AND (content LIKE '%web_search%' OR content LIKE '%web_fetch%' OR content LIKE '%remember%'
|
|
12871
|
+
OR content LIKE '%ask_keeper%' OR content LIKE '%inbox_send%'
|
|
12872
|
+
OR content LIKE '%update_progress%' OR content LIKE '%complete_goal%'
|
|
12873
|
+
OR content LIKE '%set_goal%')
|
|
12874
|
+
`).get(workerId, lastNCycles);
|
|
12875
|
+
return row.cnt;
|
|
12876
|
+
}
|
|
12859
12877
|
function cleanupStaleCycles(db2) {
|
|
12860
12878
|
const result = db2.prepare(
|
|
12861
12879
|
"UPDATE worker_cycles SET status = 'failed', error_message = 'Server restarted', finished_at = datetime('now','localtime') WHERE status = 'running'"
|
|
@@ -23981,33 +23999,131 @@ async function closeBrowser() {
|
|
|
23981
23999
|
}
|
|
23982
24000
|
}
|
|
23983
24001
|
async function webFetch(url) {
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
"Accept": "text/plain",
|
|
23988
|
-
|
|
23989
|
-
}
|
|
23990
|
-
|
|
24002
|
+
try {
|
|
24003
|
+
const jinaUrl = `https://r.jina.ai/${url}`;
|
|
24004
|
+
const response = await fetch(jinaUrl, {
|
|
24005
|
+
headers: { "Accept": "text/plain", "X-No-Cache": "true" },
|
|
24006
|
+
signal: AbortSignal.timeout(2e4)
|
|
24007
|
+
});
|
|
24008
|
+
if (response.ok) {
|
|
24009
|
+
const text = await response.text();
|
|
24010
|
+
if (text.length > 200 && !text.includes("Warning: Target URL returned error")) {
|
|
24011
|
+
return text.slice(0, MAX_CONTENT_CHARS);
|
|
24012
|
+
}
|
|
24013
|
+
}
|
|
24014
|
+
} catch {
|
|
24015
|
+
}
|
|
24016
|
+
return fetchWithBrowser(url);
|
|
24017
|
+
}
|
|
24018
|
+
async function fetchWithBrowser(url) {
|
|
24019
|
+
const browser = await getBrowser();
|
|
24020
|
+
const context = await browser.newContext({
|
|
24021
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
23991
24022
|
});
|
|
23992
|
-
|
|
23993
|
-
|
|
24023
|
+
const page = await context.newPage();
|
|
24024
|
+
try {
|
|
24025
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
24026
|
+
const text = await page.innerText("body").catch(() => "");
|
|
24027
|
+
if (!text) throw new Error(`Could not read content from ${url}`);
|
|
24028
|
+
return text.slice(0, MAX_CONTENT_CHARS);
|
|
24029
|
+
} finally {
|
|
24030
|
+
await context.close();
|
|
23994
24031
|
}
|
|
23995
|
-
const text = await response.text();
|
|
23996
|
-
return text.slice(0, MAX_CONTENT_CHARS);
|
|
23997
24032
|
}
|
|
23998
24033
|
async function webSearch(query) {
|
|
23999
|
-
const
|
|
24000
|
-
|
|
24001
|
-
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
24034
|
+
const browserResults = await searchWithBrowser(query);
|
|
24035
|
+
if (browserResults.length > 0) return browserResults;
|
|
24036
|
+
const ddgResults = await searchDdg(query);
|
|
24037
|
+
if (ddgResults.length > 0) return ddgResults;
|
|
24038
|
+
try {
|
|
24039
|
+
const response = await fetch(`https://s.jina.ai/${encodeURIComponent(query)}`, {
|
|
24040
|
+
headers: { "Accept": "application/json", "X-No-Cache": "true" },
|
|
24041
|
+
signal: AbortSignal.timeout(15e3)
|
|
24042
|
+
});
|
|
24043
|
+
if (response.ok) {
|
|
24044
|
+
const data = await response.json();
|
|
24045
|
+
if (data.data && Array.isArray(data.data)) {
|
|
24046
|
+
return data.data.slice(0, 5).map((r) => ({
|
|
24047
|
+
title: r.title ?? "",
|
|
24048
|
+
url: r.url ?? "",
|
|
24049
|
+
snippet: (r.description ?? r.content ?? "").slice(0, 300)
|
|
24050
|
+
})).filter((r) => r.url);
|
|
24051
|
+
}
|
|
24052
|
+
}
|
|
24053
|
+
} catch {
|
|
24054
|
+
}
|
|
24055
|
+
return [];
|
|
24056
|
+
}
|
|
24057
|
+
async function searchWithBrowser(query) {
|
|
24058
|
+
let browser;
|
|
24059
|
+
try {
|
|
24060
|
+
browser = await getBrowser();
|
|
24061
|
+
} catch {
|
|
24062
|
+
return [];
|
|
24063
|
+
}
|
|
24064
|
+
const context = await browser.newContext({
|
|
24065
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
24066
|
+
locale: "en-US"
|
|
24005
24067
|
});
|
|
24006
|
-
|
|
24007
|
-
|
|
24068
|
+
const page = await context.newPage();
|
|
24069
|
+
try {
|
|
24070
|
+
await page.goto(`https://search.yahoo.com/search?p=${encodeURIComponent(query)}`, {
|
|
24071
|
+
waitUntil: "domcontentloaded",
|
|
24072
|
+
timeout: 15e3
|
|
24073
|
+
});
|
|
24074
|
+
await page.waitForTimeout(1e3);
|
|
24075
|
+
const results = await page.evaluate(() => {
|
|
24076
|
+
const items = [];
|
|
24077
|
+
const blocks = document.querySelectorAll("#web .algo, .dd.algo, .algo");
|
|
24078
|
+
for (const block of blocks) {
|
|
24079
|
+
const link = block.querySelector("a");
|
|
24080
|
+
const h3 = block.querySelector("h3");
|
|
24081
|
+
const snippetEl = block.querySelector(".compText p, .compText, p");
|
|
24082
|
+
if (!link) continue;
|
|
24083
|
+
const url = link.getAttribute("href") || "";
|
|
24084
|
+
if (!url.startsWith("http")) continue;
|
|
24085
|
+
items.push({
|
|
24086
|
+
title: h3 ? (h3.textContent || "").trim() : (link.textContent || "").trim(),
|
|
24087
|
+
url,
|
|
24088
|
+
snippet: snippetEl ? (snippetEl.textContent || "").trim().slice(0, 300) : ""
|
|
24089
|
+
});
|
|
24090
|
+
if (items.length >= 5) break;
|
|
24091
|
+
}
|
|
24092
|
+
return items;
|
|
24093
|
+
});
|
|
24094
|
+
return results.filter((r) => r.url);
|
|
24095
|
+
} catch {
|
|
24096
|
+
return [];
|
|
24097
|
+
} finally {
|
|
24098
|
+
await context.close();
|
|
24099
|
+
}
|
|
24100
|
+
}
|
|
24101
|
+
async function searchDdg(query) {
|
|
24102
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
24103
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
24104
|
+
try {
|
|
24105
|
+
const response = await fetch("https://html.duckduckgo.com/html/", {
|
|
24106
|
+
method: "POST",
|
|
24107
|
+
headers: {
|
|
24108
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
24109
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
24110
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
24111
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
24112
|
+
"Referer": "https://html.duckduckgo.com/"
|
|
24113
|
+
},
|
|
24114
|
+
body: `q=${encodeURIComponent(query)}&b=`,
|
|
24115
|
+
signal: AbortSignal.timeout(15e3),
|
|
24116
|
+
redirect: "follow"
|
|
24117
|
+
});
|
|
24118
|
+
if (response.status === 202) continue;
|
|
24119
|
+
if (!response.ok) continue;
|
|
24120
|
+
const html = await response.text();
|
|
24121
|
+
const results = parseDdgResults(html).slice(0, 5);
|
|
24122
|
+
if (results.length > 0) return results;
|
|
24123
|
+
} catch {
|
|
24124
|
+
}
|
|
24008
24125
|
}
|
|
24009
|
-
|
|
24010
|
-
return parseDdgResults(html).slice(0, 5);
|
|
24126
|
+
return [];
|
|
24011
24127
|
}
|
|
24012
24128
|
function parseDdgResults(html) {
|
|
24013
24129
|
const results = [];
|
|
@@ -24402,6 +24518,47 @@ var QUEEN_TOOL_DEFINITIONS = [
|
|
|
24402
24518
|
required: ["url", "actions"]
|
|
24403
24519
|
}
|
|
24404
24520
|
}
|
|
24521
|
+
},
|
|
24522
|
+
// ── Wallet ──────────────────────────────────────────────────────────────
|
|
24523
|
+
{
|
|
24524
|
+
type: "function",
|
|
24525
|
+
function: {
|
|
24526
|
+
name: "quoroom_wallet_balance",
|
|
24527
|
+
description: "Get the room's wallet balance (USDC). Returns address and transaction summary.",
|
|
24528
|
+
parameters: {
|
|
24529
|
+
type: "object",
|
|
24530
|
+
properties: {},
|
|
24531
|
+
required: []
|
|
24532
|
+
}
|
|
24533
|
+
}
|
|
24534
|
+
},
|
|
24535
|
+
{
|
|
24536
|
+
type: "function",
|
|
24537
|
+
function: {
|
|
24538
|
+
name: "quoroom_wallet_send",
|
|
24539
|
+
description: "Send USDC from the room's wallet to an address.",
|
|
24540
|
+
parameters: {
|
|
24541
|
+
type: "object",
|
|
24542
|
+
properties: {
|
|
24543
|
+
to: { type: "string", description: "Recipient address (0x...)" },
|
|
24544
|
+
amount: { type: "string", description: 'Amount (e.g., "10.50")' }
|
|
24545
|
+
},
|
|
24546
|
+
required: ["to", "amount"]
|
|
24547
|
+
}
|
|
24548
|
+
}
|
|
24549
|
+
},
|
|
24550
|
+
{
|
|
24551
|
+
type: "function",
|
|
24552
|
+
function: {
|
|
24553
|
+
name: "quoroom_wallet_history",
|
|
24554
|
+
description: "Get recent wallet transaction history.",
|
|
24555
|
+
parameters: {
|
|
24556
|
+
type: "object",
|
|
24557
|
+
properties: {
|
|
24558
|
+
limit: { type: "string", description: "Max transactions to return (default: 10)" }
|
|
24559
|
+
}
|
|
24560
|
+
}
|
|
24561
|
+
}
|
|
24405
24562
|
}
|
|
24406
24563
|
];
|
|
24407
24564
|
async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
@@ -24422,10 +24579,12 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24422
24579
|
if (goalCheck.roomId !== roomId) return { content: `Error: goal #${goalId} belongs to another room. Your room's goals are shown in the Active Goals section \u2014 use those goal IDs.`, isError: true };
|
|
24423
24580
|
const observation = String(args.observation ?? args.progress ?? args.message ?? args.text ?? "");
|
|
24424
24581
|
const metricValue = args.metricValue != null ? Number(args.metricValue) : args.metric_value != null ? Number(args.metric_value) : void 0;
|
|
24582
|
+
const subGoals = getSubGoals(db2, goalId);
|
|
24425
24583
|
updateGoalProgress(db2, goalId, observation, metricValue, workerId);
|
|
24426
24584
|
const goal = getGoal(db2, goalId);
|
|
24427
24585
|
const pct = Math.round((goal?.progress ?? 0) * 100);
|
|
24428
|
-
|
|
24586
|
+
const note = subGoals.length > 0 && metricValue != null ? ` (metricValue ignored \u2014 goal has ${subGoals.length} sub-goals, progress is calculated from them. Update sub-goals directly.)` : "";
|
|
24587
|
+
return { content: `Progress logged on goal #${goalId}. Now at ${pct}%.${note}` };
|
|
24429
24588
|
}
|
|
24430
24589
|
case "quoroom_create_subgoal": {
|
|
24431
24590
|
const goalId = Number(args.goalId);
|
|
@@ -24458,6 +24617,13 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24458
24617
|
case "quoroom_propose": {
|
|
24459
24618
|
const proposalText = String(args.proposal ?? args.text ?? args.description ?? args.content ?? args.idea ?? "").trim();
|
|
24460
24619
|
if (!proposalText) return { content: 'Error: proposal text is required. Provide a "proposal" string.', isError: true };
|
|
24620
|
+
const recentDecisions = listDecisions(db2, roomId);
|
|
24621
|
+
const isDuplicate = recentDecisions.slice(0, 10).some(
|
|
24622
|
+
(d) => (d.status === "voting" || d.status === "approved") && d.proposal.toLowerCase() === proposalText.toLowerCase()
|
|
24623
|
+
);
|
|
24624
|
+
if (isDuplicate) {
|
|
24625
|
+
return { content: `A similar proposal already exists: "${proposalText}". No need to propose again.`, isError: true };
|
|
24626
|
+
}
|
|
24461
24627
|
const decisionType = String(args.decisionType ?? args.type ?? args.impact ?? args.category ?? "low_impact");
|
|
24462
24628
|
const decision = propose(db2, {
|
|
24463
24629
|
roomId,
|
|
@@ -24498,6 +24664,10 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24498
24664
|
const systemPrompt = String(args.systemPrompt ?? args.system_prompt ?? args.instructions ?? args.prompt ?? "").trim();
|
|
24499
24665
|
if (!name) return { content: 'Error: name is required for quoroom_create_worker. Provide a "name" string.', isError: true };
|
|
24500
24666
|
if (!systemPrompt) return { content: `Error: systemPrompt is required for quoroom_create_worker. Provide a "systemPrompt" string describing this worker's role and instructions.`, isError: true };
|
|
24667
|
+
const existingWorkers = listRoomWorkers(db2, roomId);
|
|
24668
|
+
if (existingWorkers.some((w) => w.name.toLowerCase() === name.toLowerCase())) {
|
|
24669
|
+
return { content: `Worker "${name}" already exists in this room. Use quoroom_update_worker to modify it, or choose a different name.`, isError: true };
|
|
24670
|
+
}
|
|
24501
24671
|
const role = args.role && args.role !== args.name ? String(args.role) : void 0;
|
|
24502
24672
|
const description = args.description ? String(args.description) : void 0;
|
|
24503
24673
|
const preset = role ? WORKER_ROLE_PRESETS[role] : void 0;
|
|
@@ -24530,6 +24700,10 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24530
24700
|
const taskWorkerId = args.workerId ? Number(args.workerId) : void 0;
|
|
24531
24701
|
const maxTurns = args.maxTurns ? Number(args.maxTurns) : void 0;
|
|
24532
24702
|
const triggerType = cronExpression ? "cron" : scheduledAt ? "once" : "manual";
|
|
24703
|
+
const existingTasks = listTasks(db2, roomId, "active");
|
|
24704
|
+
if (existingTasks.some((t) => t.name.toLowerCase() === name.toLowerCase())) {
|
|
24705
|
+
return { content: `Task "${name}" already exists. Choose a different name or manage the existing task.`, isError: true };
|
|
24706
|
+
}
|
|
24533
24707
|
if (taskWorkerId) {
|
|
24534
24708
|
const taskWorker = getWorker(db2, taskWorkerId);
|
|
24535
24709
|
if (!taskWorker || taskWorker.roomId !== roomId) {
|
|
@@ -24555,6 +24729,11 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24555
24729
|
const name = String(args.name ?? "");
|
|
24556
24730
|
const content = String(args.content ?? "");
|
|
24557
24731
|
const type = String(args.type ?? "fact");
|
|
24732
|
+
const existing = listEntities(db2, roomId).find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
24733
|
+
if (existing) {
|
|
24734
|
+
addObservation(db2, existing.id, content, "queen");
|
|
24735
|
+
return { content: `Updated memory "${name}" (added new observation to existing entry).` };
|
|
24736
|
+
}
|
|
24558
24737
|
const entity = createEntity(db2, name, type, void 0, roomId);
|
|
24559
24738
|
addObservation(db2, entity.id, content, "queen");
|
|
24560
24739
|
return { content: `Remembered "${name}".` };
|
|
@@ -24609,6 +24788,26 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24609
24788
|
if (!url) return { content: "Error: url is required", isError: true };
|
|
24610
24789
|
return { content: await browserAction(url, actions) };
|
|
24611
24790
|
}
|
|
24791
|
+
// ── Wallet ────────────────────────────────────────────────────
|
|
24792
|
+
case "quoroom_wallet_balance": {
|
|
24793
|
+
const wallet = getWalletByRoom(db2, roomId);
|
|
24794
|
+
if (!wallet) return { content: "No wallet found for this room.", isError: true };
|
|
24795
|
+
const summary = getWalletTransactionSummary(db2, wallet.id);
|
|
24796
|
+
const net = (parseFloat(summary.received) - parseFloat(summary.sent)).toFixed(2);
|
|
24797
|
+
return { content: `Wallet ${wallet.address}: ${net} USDC (received: ${summary.received}, sent: ${summary.sent})` };
|
|
24798
|
+
}
|
|
24799
|
+
case "quoroom_wallet_send": {
|
|
24800
|
+
return { content: "Wallet send requires on-chain transaction \u2014 use the MCP tool quoroom_wallet_send with encryptionKey, or ask the keeper to send funds.", isError: true };
|
|
24801
|
+
}
|
|
24802
|
+
case "quoroom_wallet_history": {
|
|
24803
|
+
const wallet = getWalletByRoom(db2, roomId);
|
|
24804
|
+
if (!wallet) return { content: "No wallet found for this room.", isError: true };
|
|
24805
|
+
const limit = Math.min(Number(args.limit) || 10, 50);
|
|
24806
|
+
const txs = listWalletTransactions(db2, wallet.id, limit);
|
|
24807
|
+
if (txs.length === 0) return { content: "No transactions yet." };
|
|
24808
|
+
const lines = txs.map((tx) => `[${tx.type}] ${tx.amount} USDC \u2014 ${tx.description ?? ""} (${tx.status})`).join("\n");
|
|
24809
|
+
return { content: lines };
|
|
24810
|
+
}
|
|
24612
24811
|
default:
|
|
24613
24812
|
return { content: `Unknown tool: ${toolName}`, isError: true };
|
|
24614
24813
|
}
|
|
@@ -25051,6 +25250,14 @@ ${roomTasks.map(
|
|
|
25051
25250
|
(t) => `- #${t.id} "${t.name}" [${t.triggerType}] \u2014 ${t.status}`
|
|
25052
25251
|
).join("\n")}`);
|
|
25053
25252
|
}
|
|
25253
|
+
const wallet = getWalletByRoom(db2, roomId);
|
|
25254
|
+
if (wallet) {
|
|
25255
|
+
const summary = getWalletTransactionSummary(db2, wallet.id);
|
|
25256
|
+
const net = (parseFloat(summary.received) - parseFloat(summary.sent)).toFixed(2);
|
|
25257
|
+
contextParts.push(`## Wallet
|
|
25258
|
+
Address: ${wallet.address}
|
|
25259
|
+
Balance: ${net} USDC (received: ${summary.received}, spent: ${summary.sent})`);
|
|
25260
|
+
}
|
|
25054
25261
|
if (unreadMessages.length > 0) {
|
|
25055
25262
|
contextParts.push(`## Unread Messages
|
|
25056
25263
|
${unreadMessages.map(
|
|
@@ -25088,19 +25295,54 @@ ${top3.map(
|
|
|
25088
25295
|
}
|
|
25089
25296
|
contextParts.push(`## Execution Settings
|
|
25090
25297
|
${settingsParts.join("\n")}`);
|
|
25298
|
+
const STUCK_THRESHOLD_CYCLES = 2;
|
|
25299
|
+
const productiveCallCount = countProductiveToolCalls(db2, worker.id, STUCK_THRESHOLD_CYCLES);
|
|
25300
|
+
const recentCompletedCycles = listRoomCycles(db2, roomId, 5).filter((c) => c.workerId === worker.id && c.status === "completed");
|
|
25301
|
+
const isStuck = recentCompletedCycles.length >= STUCK_THRESHOLD_CYCLES && productiveCallCount === 0;
|
|
25302
|
+
if (isStuck) {
|
|
25303
|
+
contextParts.push(`## \u26A0 STUCK DETECTED
|
|
25304
|
+
Your last ${STUCK_THRESHOLD_CYCLES} cycles produced no external results (no web searches, no memories stored, no goal progress, no keeper messages). You MUST change strategy NOW:
|
|
25305
|
+
- Try a different web search query
|
|
25306
|
+
- Store what you know in memory even if incomplete
|
|
25307
|
+
- Update goal progress with what you've learned
|
|
25308
|
+
- Message the keeper if you're blocked
|
|
25309
|
+
Do NOT repeat the same approach. Pivot immediately.`);
|
|
25310
|
+
logBuffer.addSynthetic("system", `Stuck detector: 0 productive tool calls in last ${STUCK_THRESHOLD_CYCLES} cycles \u2014 injecting pivot directive`);
|
|
25311
|
+
}
|
|
25091
25312
|
const selfRegulateHint = rateLimitEvents.length > 0 ? "\n- **Self-regulate**: You are hitting rate limits. Use quoroom_configure_room to increase your cycle gap or reduce max turns to stay within API limits." : "";
|
|
25092
25313
|
const isClaude = model === "claude" || model.startsWith("claude-");
|
|
25093
25314
|
const toolCallInstruction = isClaude ? "Always call tools to take action \u2014 do not just describe what you would do." : "IMPORTANT: You MUST call at least one tool in your response. Respond ONLY with a tool call \u2014 do not write explanatory text without a tool call.";
|
|
25094
|
-
const
|
|
25095
|
-
const
|
|
25096
|
-
const
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25315
|
+
const allowListRaw = status2.room.allowedTools?.trim() || null;
|
|
25316
|
+
const allowSet = allowListRaw ? new Set(allowListRaw.split(",").map((s) => s.trim())) : null;
|
|
25317
|
+
const has = (name) => !allowSet || allowSet.has(name);
|
|
25318
|
+
const toolLines = [];
|
|
25319
|
+
const goalTools = ["quoroom_set_goal", "quoroom_update_progress", "quoroom_create_subgoal", "quoroom_complete_goal", "quoroom_abandon_goal"].filter(has);
|
|
25320
|
+
if (goalTools.length) toolLines.push(`**Goals:** ${goalTools.join(", ")}`);
|
|
25321
|
+
const govTools = ["quoroom_propose", "quoroom_vote"].filter(has);
|
|
25322
|
+
if (govTools.length) toolLines.push(`**Governance:** ${govTools.join(", ")}`);
|
|
25323
|
+
const workerTools = ["quoroom_create_worker", "quoroom_update_worker"].filter(has);
|
|
25324
|
+
if (workerTools.length) toolLines.push(`**Workers:** ${workerTools.join(", ")}`);
|
|
25325
|
+
if (has("quoroom_schedule")) toolLines.push("**Tasks:** quoroom_schedule");
|
|
25326
|
+
const memTools = ["quoroom_remember", "quoroom_recall"].filter(has);
|
|
25327
|
+
if (memTools.length) toolLines.push(`**Memory:** ${memTools.join(", ")}`);
|
|
25328
|
+
const walletToolNames = isCli ? ["quoroom_wallet_balance", "quoroom_wallet_send", "quoroom_wallet_history", "quoroom_wallet_topup"] : ["quoroom_wallet_balance", "quoroom_wallet_send", "quoroom_wallet_history"];
|
|
25329
|
+
const filteredWallet = walletToolNames.filter(has);
|
|
25330
|
+
if (filteredWallet.length) toolLines.push(`**Wallet:** ${filteredWallet.join(", ")}`);
|
|
25331
|
+
const webToolNames = isCli ? null : ["quoroom_web_search", "quoroom_web_fetch", "quoroom_browser"];
|
|
25332
|
+
if (isCli) {
|
|
25333
|
+
if (has("quoroom_web_search") || has("quoroom_web_fetch")) toolLines.push("**Web:** (use your built-in web search and fetch tools)");
|
|
25334
|
+
} else {
|
|
25335
|
+
const filteredWeb = (webToolNames || []).filter(has);
|
|
25336
|
+
if (filteredWeb.length) toolLines.push(`**Web:** ${filteredWeb.join(", ")}`);
|
|
25337
|
+
}
|
|
25338
|
+
const commsToolNames = isCli ? ["quoroom_inbox_send_keeper", "quoroom_inbox_list", "quoroom_inbox_send_room", "quoroom_inbox_reply"] : ["quoroom_ask_keeper"];
|
|
25339
|
+
const filteredComms = commsToolNames.filter(has);
|
|
25340
|
+
if (filteredComms.length) {
|
|
25341
|
+
if (isCli) toolLines.push(`**Comms:** ${filteredComms.map((t) => t === "quoroom_inbox_send_keeper" ? `${t} (message keeper)` : t === "quoroom_inbox_list" ? `${t} (inter-room)` : t).join(", ")}`);
|
|
25342
|
+
else toolLines.push(`**Comms:** ${filteredComms.join(", ")}`);
|
|
25343
|
+
}
|
|
25344
|
+
if (has("quoroom_configure_room")) toolLines.push(`**Settings:** quoroom_configure_room${selfRegulateHint}`);
|
|
25345
|
+
const toolList = toolLines.join("\n");
|
|
25104
25346
|
const sendKeeperTool = isCli ? "quoroom_inbox_send_keeper" : "quoroom_ask_keeper";
|
|
25105
25347
|
contextParts.push(`## Instructions
|
|
25106
25348
|
Based on the current state, decide what to do next and call the appropriate tools. Available tools:
|
|
@@ -25109,6 +25351,8 @@ ${toolList}
|
|
|
25109
25351
|
|
|
25110
25352
|
Do NOT "stand by" or wait for anyone \u2014 every cycle must make progress. Act autonomously: make decisions and execute. Inform the keeper of progress or important updates using ${sendKeeperTool}, but never block on a response. If the keeper hasn't replied, proceed with your best judgment.
|
|
25111
25353
|
|
|
25354
|
+
Revenue is always a priority. Every room must sustain itself financially. Actively seek ways to earn: offer services to the keeper, propose paid work to other rooms, or find monetizable opportunities in your domain. Check your wallet balance and report financial status to the keeper.
|
|
25355
|
+
|
|
25112
25356
|
${toolCallInstruction}`);
|
|
25113
25357
|
const prompt = contextParts.join("\n\n");
|
|
25114
25358
|
updateAgentState(db2, worker.id, "acting");
|
|
@@ -25117,8 +25361,9 @@ ${toolCallInstruction}`);
|
|
|
25117
25361
|
logBuffer.flush();
|
|
25118
25362
|
const apiKey = apiKeyEarly;
|
|
25119
25363
|
const needsQueenTools = model === "openai" || model.startsWith("openai:") || model === "anthropic" || model.startsWith("anthropic:") || model.startsWith("claude-api:");
|
|
25364
|
+
const filteredToolDefs = allowSet ? QUEEN_TOOL_DEFINITIONS.filter((t) => allowSet.has(t.function.name)) : QUEEN_TOOL_DEFINITIONS;
|
|
25120
25365
|
const apiToolOpts = needsQueenTools ? {
|
|
25121
|
-
toolDefs:
|
|
25366
|
+
toolDefs: filteredToolDefs,
|
|
25122
25367
|
onToolCall: async (toolName, args) => {
|
|
25123
25368
|
logBuffer.addSynthetic("tool_call", `\u2192 ${toolName}(${JSON.stringify(args)})`);
|
|
25124
25369
|
const result2 = await executeQueenTool(db2, roomId, worker.id, toolName, args);
|
|
@@ -25134,6 +25379,8 @@ ${toolCallInstruction}`);
|
|
|
25134
25379
|
timeoutMs: 5 * 60 * 1e3,
|
|
25135
25380
|
maxTurns: maxTurns ?? 10,
|
|
25136
25381
|
onConsoleLog: logBuffer.onConsoleLog,
|
|
25382
|
+
// CLI models: block non-quoroom MCP tools (daymon, etc.)
|
|
25383
|
+
disallowedTools: isCli ? "mcp__daymon*" : void 0,
|
|
25137
25384
|
// CLI models: pass resumeSessionId for native --resume
|
|
25138
25385
|
resumeSessionId,
|
|
25139
25386
|
// API models: pass conversation history + persistence callback
|
|
@@ -25148,6 +25395,23 @@ ${toolCallInstruction}`);
|
|
|
25148
25395
|
if (rateLimitInfo) {
|
|
25149
25396
|
throw new RateLimitError(rateLimitInfo);
|
|
25150
25397
|
}
|
|
25398
|
+
if (result.exitCode !== 0) {
|
|
25399
|
+
const errorDetail = result.output?.trim() || `exit code ${result.exitCode}`;
|
|
25400
|
+
logBuffer.addSynthetic("error", `Agent execution failed: ${errorDetail.slice(0, 500)}`);
|
|
25401
|
+
logBuffer.flush();
|
|
25402
|
+
completeWorkerCycle(db2, cycle.id, errorDetail.slice(0, 500), result.usage);
|
|
25403
|
+
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
25404
|
+
logRoomActivity(
|
|
25405
|
+
db2,
|
|
25406
|
+
roomId,
|
|
25407
|
+
"error",
|
|
25408
|
+
`Agent cycle failed (${worker.name}): ${errorDetail.slice(0, 200)}`,
|
|
25409
|
+
errorDetail,
|
|
25410
|
+
worker.id
|
|
25411
|
+
);
|
|
25412
|
+
updateAgentState(db2, worker.id, "idle");
|
|
25413
|
+
return result.output;
|
|
25414
|
+
}
|
|
25151
25415
|
if (isCli && result.sessionId) {
|
|
25152
25416
|
saveAgentSession(db2, worker.id, { sessionId: result.sessionId, model });
|
|
25153
25417
|
}
|
|
@@ -25556,6 +25820,7 @@ function registerRoomRoutes(router) {
|
|
|
25556
25820
|
const trimmed = body.queenNickname.trim().replace(/\s+/g, "");
|
|
25557
25821
|
if (trimmed.length > 0 && trimmed.length <= 40) updates.queenNickname = trimmed;
|
|
25558
25822
|
}
|
|
25823
|
+
if (body.allowedTools !== void 0) updates.allowedTools = body.allowedTools || null;
|
|
25559
25824
|
if (body.config !== void 0 && typeof body.config === "object" && body.config !== null) {
|
|
25560
25825
|
updates.config = { ...room.config, ...body.config };
|
|
25561
25826
|
}
|
|
@@ -28047,7 +28312,15 @@ function registerChatRoutes(router) {
|
|
|
28047
28312
|
// 3 minutes
|
|
28048
28313
|
});
|
|
28049
28314
|
if (result.exitCode !== 0 || result.timedOut) {
|
|
28050
|
-
const
|
|
28315
|
+
const rawOutput = result.output?.trim();
|
|
28316
|
+
let reason;
|
|
28317
|
+
if (result.timedOut) {
|
|
28318
|
+
reason = "Chat request timed out";
|
|
28319
|
+
} else if (rawOutput) {
|
|
28320
|
+
reason = rawOutput;
|
|
28321
|
+
} else {
|
|
28322
|
+
reason = `Chat execution failed (model: ${model}, exit code: ${result.exitCode})`;
|
|
28323
|
+
}
|
|
28051
28324
|
return { status: result.timedOut ? 504 : 502, error: reason.slice(0, 500) };
|
|
28052
28325
|
}
|
|
28053
28326
|
const response = result.output || "No response";
|
|
@@ -28134,6 +28407,7 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|
|
28134
28407
|
queen_nickname TEXT,
|
|
28135
28408
|
chat_session_id TEXT,
|
|
28136
28409
|
referred_by_code TEXT,
|
|
28410
|
+
allowed_tools TEXT,
|
|
28137
28411
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
28138
28412
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
28139
28413
|
);
|
|
@@ -28618,6 +28892,13 @@ function runMigrations(database, log = console.log) {
|
|
|
28618
28892
|
database.exec(`ALTER TABLE workers ADD COLUMN max_turns INTEGER`);
|
|
28619
28893
|
log("Migrated: added cycle_gap_ms and max_turns columns to workers");
|
|
28620
28894
|
}
|
|
28895
|
+
const hasRoomAllowedTools = database.prepare(
|
|
28896
|
+
`SELECT name FROM pragma_table_info('rooms') WHERE name='allowed_tools'`
|
|
28897
|
+
).get()?.name;
|
|
28898
|
+
if (!hasRoomAllowedTools) {
|
|
28899
|
+
database.exec(`ALTER TABLE rooms ADD COLUMN allowed_tools TEXT`);
|
|
28900
|
+
log("Migrated: added allowed_tools column to rooms");
|
|
28901
|
+
}
|
|
28621
28902
|
const ollamaWorkers = database.prepare(`SELECT id FROM workers WHERE model LIKE 'ollama:%'`).all();
|
|
28622
28903
|
if (ollamaWorkers.length > 0) {
|
|
28623
28904
|
database.prepare(`UPDATE workers SET model = 'claude' WHERE model LIKE 'ollama:%'`).run();
|
|
@@ -28835,7 +29116,7 @@ function semverGt(a, b) {
|
|
|
28835
29116
|
}
|
|
28836
29117
|
function getCurrentVersion() {
|
|
28837
29118
|
try {
|
|
28838
|
-
return true ? "0.1.
|
|
29119
|
+
return true ? "0.1.22" : null.version;
|
|
28839
29120
|
} catch {
|
|
28840
29121
|
return "0.0.0";
|
|
28841
29122
|
}
|
|
@@ -28992,7 +29273,7 @@ var cachedVersion = null;
|
|
|
28992
29273
|
function getVersion3() {
|
|
28993
29274
|
if (cachedVersion) return cachedVersion;
|
|
28994
29275
|
try {
|
|
28995
|
-
cachedVersion = true ? "0.1.
|
|
29276
|
+
cachedVersion = true ? "0.1.22" : null.version;
|
|
28996
29277
|
} catch {
|
|
28997
29278
|
cachedVersion = "unknown";
|
|
28998
29279
|
}
|
package/out/mcp/cli.js
CHANGED
|
@@ -21632,6 +21632,7 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|
|
21632
21632
|
queen_nickname TEXT,
|
|
21633
21633
|
chat_session_id TEXT,
|
|
21634
21634
|
referred_by_code TEXT,
|
|
21635
|
+
allowed_tools TEXT,
|
|
21635
21636
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
21636
21637
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
21637
21638
|
);
|
|
@@ -22961,6 +22962,7 @@ function mapRoomRow(row) {
|
|
|
22961
22962
|
queenNickname: row.queen_nickname ?? null,
|
|
22962
22963
|
chatSessionId: row.chat_session_id ?? null,
|
|
22963
22964
|
referredByCode: row.referred_by_code ?? null,
|
|
22965
|
+
allowedTools: row.allowed_tools ?? null,
|
|
22964
22966
|
webhookToken: row.webhook_token ?? null,
|
|
22965
22967
|
createdAt: row.created_at,
|
|
22966
22968
|
updatedAt: row.updated_at
|
|
@@ -23011,6 +23013,7 @@ function updateRoom(db3, id, updates) {
|
|
|
23011
23013
|
config: "config",
|
|
23012
23014
|
referredByCode: "referred_by_code",
|
|
23013
23015
|
queenNickname: "queen_nickname",
|
|
23016
|
+
allowedTools: "allowed_tools",
|
|
23014
23017
|
webhookToken: "webhook_token"
|
|
23015
23018
|
};
|
|
23016
23019
|
const fields = [];
|
|
@@ -23725,6 +23728,22 @@ function listRoomCycles(db3, roomId, limit = 20) {
|
|
|
23725
23728
|
).all(roomId, safeLimit);
|
|
23726
23729
|
return rows.map(mapWorkerCycleRow);
|
|
23727
23730
|
}
|
|
23731
|
+
function countProductiveToolCalls(db3, workerId, lastNCycles = 2) {
|
|
23732
|
+
const row = db3.prepare(`
|
|
23733
|
+
SELECT COUNT(*) as cnt FROM cycle_logs
|
|
23734
|
+
WHERE cycle_id IN (
|
|
23735
|
+
SELECT id FROM worker_cycles
|
|
23736
|
+
WHERE worker_id = ? AND status = 'completed'
|
|
23737
|
+
ORDER BY started_at DESC LIMIT ?
|
|
23738
|
+
)
|
|
23739
|
+
AND entry_type = 'tool_call'
|
|
23740
|
+
AND (content LIKE '%web_search%' OR content LIKE '%web_fetch%' OR content LIKE '%remember%'
|
|
23741
|
+
OR content LIKE '%ask_keeper%' OR content LIKE '%inbox_send%'
|
|
23742
|
+
OR content LIKE '%update_progress%' OR content LIKE '%complete_goal%'
|
|
23743
|
+
OR content LIKE '%set_goal%')
|
|
23744
|
+
`).get(workerId, lastNCycles);
|
|
23745
|
+
return row.cnt;
|
|
23746
|
+
}
|
|
23728
23747
|
function cleanupStaleCycles(db3) {
|
|
23729
23748
|
const result = db3.prepare(
|
|
23730
23749
|
"UPDATE worker_cycles SET status = 'failed', error_message = 'Server restarted', finished_at = datetime('now','localtime') WHERE status = 'running'"
|
|
@@ -23967,6 +23986,13 @@ function runMigrations(database, log = console.log) {
|
|
|
23967
23986
|
database.exec(`ALTER TABLE workers ADD COLUMN max_turns INTEGER`);
|
|
23968
23987
|
log("Migrated: added cycle_gap_ms and max_turns columns to workers");
|
|
23969
23988
|
}
|
|
23989
|
+
const hasRoomAllowedTools = database.prepare(
|
|
23990
|
+
`SELECT name FROM pragma_table_info('rooms') WHERE name='allowed_tools'`
|
|
23991
|
+
).get()?.name;
|
|
23992
|
+
if (!hasRoomAllowedTools) {
|
|
23993
|
+
database.exec(`ALTER TABLE rooms ADD COLUMN allowed_tools TEXT`);
|
|
23994
|
+
log("Migrated: added allowed_tools column to rooms");
|
|
23995
|
+
}
|
|
23970
23996
|
const ollamaWorkers = database.prepare(`SELECT id FROM workers WHERE model LIKE 'ollama:%'`).all();
|
|
23971
23997
|
if (ollamaWorkers.length > 0) {
|
|
23972
23998
|
database.prepare(`UPDATE workers SET model = 'claude' WHERE model LIKE 'ollama:%'`).run();
|
|
@@ -51046,7 +51072,7 @@ var server_exports = {};
|
|
|
51046
51072
|
async function main() {
|
|
51047
51073
|
const server = new McpServer({
|
|
51048
51074
|
name: "quoroom",
|
|
51049
|
-
version: true ? "0.1.
|
|
51075
|
+
version: true ? "0.1.22" : "0.0.0"
|
|
51050
51076
|
});
|
|
51051
51077
|
registerMemoryTools(server);
|
|
51052
51078
|
registerSchedulerTools(server);
|
|
@@ -51630,33 +51656,131 @@ async function closeBrowser() {
|
|
|
51630
51656
|
}
|
|
51631
51657
|
}
|
|
51632
51658
|
async function webFetch(url) {
|
|
51633
|
-
|
|
51634
|
-
|
|
51635
|
-
|
|
51636
|
-
"Accept": "text/plain",
|
|
51637
|
-
|
|
51638
|
-
}
|
|
51639
|
-
|
|
51659
|
+
try {
|
|
51660
|
+
const jinaUrl = `https://r.jina.ai/${url}`;
|
|
51661
|
+
const response = await fetch(jinaUrl, {
|
|
51662
|
+
headers: { "Accept": "text/plain", "X-No-Cache": "true" },
|
|
51663
|
+
signal: AbortSignal.timeout(2e4)
|
|
51664
|
+
});
|
|
51665
|
+
if (response.ok) {
|
|
51666
|
+
const text = await response.text();
|
|
51667
|
+
if (text.length > 200 && !text.includes("Warning: Target URL returned error")) {
|
|
51668
|
+
return text.slice(0, MAX_CONTENT_CHARS);
|
|
51669
|
+
}
|
|
51670
|
+
}
|
|
51671
|
+
} catch {
|
|
51672
|
+
}
|
|
51673
|
+
return fetchWithBrowser(url);
|
|
51674
|
+
}
|
|
51675
|
+
async function fetchWithBrowser(url) {
|
|
51676
|
+
const browser = await getBrowser();
|
|
51677
|
+
const context = await browser.newContext({
|
|
51678
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
51640
51679
|
});
|
|
51641
|
-
|
|
51642
|
-
|
|
51680
|
+
const page = await context.newPage();
|
|
51681
|
+
try {
|
|
51682
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
51683
|
+
const text = await page.innerText("body").catch(() => "");
|
|
51684
|
+
if (!text) throw new Error(`Could not read content from ${url}`);
|
|
51685
|
+
return text.slice(0, MAX_CONTENT_CHARS);
|
|
51686
|
+
} finally {
|
|
51687
|
+
await context.close();
|
|
51643
51688
|
}
|
|
51644
|
-
const text = await response.text();
|
|
51645
|
-
return text.slice(0, MAX_CONTENT_CHARS);
|
|
51646
51689
|
}
|
|
51647
51690
|
async function webSearch(query) {
|
|
51648
|
-
const
|
|
51649
|
-
|
|
51650
|
-
|
|
51651
|
-
|
|
51652
|
-
|
|
51653
|
-
|
|
51691
|
+
const browserResults = await searchWithBrowser(query);
|
|
51692
|
+
if (browserResults.length > 0) return browserResults;
|
|
51693
|
+
const ddgResults = await searchDdg(query);
|
|
51694
|
+
if (ddgResults.length > 0) return ddgResults;
|
|
51695
|
+
try {
|
|
51696
|
+
const response = await fetch(`https://s.jina.ai/${encodeURIComponent(query)}`, {
|
|
51697
|
+
headers: { "Accept": "application/json", "X-No-Cache": "true" },
|
|
51698
|
+
signal: AbortSignal.timeout(15e3)
|
|
51699
|
+
});
|
|
51700
|
+
if (response.ok) {
|
|
51701
|
+
const data = await response.json();
|
|
51702
|
+
if (data.data && Array.isArray(data.data)) {
|
|
51703
|
+
return data.data.slice(0, 5).map((r) => ({
|
|
51704
|
+
title: r.title ?? "",
|
|
51705
|
+
url: r.url ?? "",
|
|
51706
|
+
snippet: (r.description ?? r.content ?? "").slice(0, 300)
|
|
51707
|
+
})).filter((r) => r.url);
|
|
51708
|
+
}
|
|
51709
|
+
}
|
|
51710
|
+
} catch {
|
|
51711
|
+
}
|
|
51712
|
+
return [];
|
|
51713
|
+
}
|
|
51714
|
+
async function searchWithBrowser(query) {
|
|
51715
|
+
let browser;
|
|
51716
|
+
try {
|
|
51717
|
+
browser = await getBrowser();
|
|
51718
|
+
} catch {
|
|
51719
|
+
return [];
|
|
51720
|
+
}
|
|
51721
|
+
const context = await browser.newContext({
|
|
51722
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
51723
|
+
locale: "en-US"
|
|
51654
51724
|
});
|
|
51655
|
-
|
|
51656
|
-
|
|
51725
|
+
const page = await context.newPage();
|
|
51726
|
+
try {
|
|
51727
|
+
await page.goto(`https://search.yahoo.com/search?p=${encodeURIComponent(query)}`, {
|
|
51728
|
+
waitUntil: "domcontentloaded",
|
|
51729
|
+
timeout: 15e3
|
|
51730
|
+
});
|
|
51731
|
+
await page.waitForTimeout(1e3);
|
|
51732
|
+
const results = await page.evaluate(() => {
|
|
51733
|
+
const items = [];
|
|
51734
|
+
const blocks = document.querySelectorAll("#web .algo, .dd.algo, .algo");
|
|
51735
|
+
for (const block of blocks) {
|
|
51736
|
+
const link = block.querySelector("a");
|
|
51737
|
+
const h3 = block.querySelector("h3");
|
|
51738
|
+
const snippetEl = block.querySelector(".compText p, .compText, p");
|
|
51739
|
+
if (!link) continue;
|
|
51740
|
+
const url = link.getAttribute("href") || "";
|
|
51741
|
+
if (!url.startsWith("http")) continue;
|
|
51742
|
+
items.push({
|
|
51743
|
+
title: h3 ? (h3.textContent || "").trim() : (link.textContent || "").trim(),
|
|
51744
|
+
url,
|
|
51745
|
+
snippet: snippetEl ? (snippetEl.textContent || "").trim().slice(0, 300) : ""
|
|
51746
|
+
});
|
|
51747
|
+
if (items.length >= 5) break;
|
|
51748
|
+
}
|
|
51749
|
+
return items;
|
|
51750
|
+
});
|
|
51751
|
+
return results.filter((r) => r.url);
|
|
51752
|
+
} catch {
|
|
51753
|
+
return [];
|
|
51754
|
+
} finally {
|
|
51755
|
+
await context.close();
|
|
51657
51756
|
}
|
|
51658
|
-
|
|
51659
|
-
|
|
51757
|
+
}
|
|
51758
|
+
async function searchDdg(query) {
|
|
51759
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
51760
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
51761
|
+
try {
|
|
51762
|
+
const response = await fetch("https://html.duckduckgo.com/html/", {
|
|
51763
|
+
method: "POST",
|
|
51764
|
+
headers: {
|
|
51765
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
51766
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
51767
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
51768
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
51769
|
+
"Referer": "https://html.duckduckgo.com/"
|
|
51770
|
+
},
|
|
51771
|
+
body: `q=${encodeURIComponent(query)}&b=`,
|
|
51772
|
+
signal: AbortSignal.timeout(15e3),
|
|
51773
|
+
redirect: "follow"
|
|
51774
|
+
});
|
|
51775
|
+
if (response.status === 202) continue;
|
|
51776
|
+
if (!response.ok) continue;
|
|
51777
|
+
const html = await response.text();
|
|
51778
|
+
const results = parseDdgResults(html).slice(0, 5);
|
|
51779
|
+
if (results.length > 0) return results;
|
|
51780
|
+
} catch {
|
|
51781
|
+
}
|
|
51782
|
+
}
|
|
51783
|
+
return [];
|
|
51660
51784
|
}
|
|
51661
51785
|
function parseDdgResults(html) {
|
|
51662
51786
|
const results = [];
|
|
@@ -51785,10 +51909,12 @@ async function executeQueenTool(db3, roomId, workerId, toolName, args2) {
|
|
|
51785
51909
|
if (goalCheck.roomId !== roomId) return { content: `Error: goal #${goalId} belongs to another room. Your room's goals are shown in the Active Goals section \u2014 use those goal IDs.`, isError: true };
|
|
51786
51910
|
const observation = String(args2.observation ?? args2.progress ?? args2.message ?? args2.text ?? "");
|
|
51787
51911
|
const metricValue = args2.metricValue != null ? Number(args2.metricValue) : args2.metric_value != null ? Number(args2.metric_value) : void 0;
|
|
51912
|
+
const subGoals = getSubGoals(db3, goalId);
|
|
51788
51913
|
updateGoalProgress(db3, goalId, observation, metricValue, workerId);
|
|
51789
51914
|
const goal = getGoal(db3, goalId);
|
|
51790
51915
|
const pct = Math.round((goal?.progress ?? 0) * 100);
|
|
51791
|
-
|
|
51916
|
+
const note = subGoals.length > 0 && metricValue != null ? ` (metricValue ignored \u2014 goal has ${subGoals.length} sub-goals, progress is calculated from them. Update sub-goals directly.)` : "";
|
|
51917
|
+
return { content: `Progress logged on goal #${goalId}. Now at ${pct}%.${note}` };
|
|
51792
51918
|
}
|
|
51793
51919
|
case "quoroom_create_subgoal": {
|
|
51794
51920
|
const goalId = Number(args2.goalId);
|
|
@@ -51821,6 +51947,13 @@ async function executeQueenTool(db3, roomId, workerId, toolName, args2) {
|
|
|
51821
51947
|
case "quoroom_propose": {
|
|
51822
51948
|
const proposalText = String(args2.proposal ?? args2.text ?? args2.description ?? args2.content ?? args2.idea ?? "").trim();
|
|
51823
51949
|
if (!proposalText) return { content: 'Error: proposal text is required. Provide a "proposal" string.', isError: true };
|
|
51950
|
+
const recentDecisions = listDecisions(db3, roomId);
|
|
51951
|
+
const isDuplicate = recentDecisions.slice(0, 10).some(
|
|
51952
|
+
(d) => (d.status === "voting" || d.status === "approved") && d.proposal.toLowerCase() === proposalText.toLowerCase()
|
|
51953
|
+
);
|
|
51954
|
+
if (isDuplicate) {
|
|
51955
|
+
return { content: `A similar proposal already exists: "${proposalText}". No need to propose again.`, isError: true };
|
|
51956
|
+
}
|
|
51824
51957
|
const decisionType = String(args2.decisionType ?? args2.type ?? args2.impact ?? args2.category ?? "low_impact");
|
|
51825
51958
|
const decision = propose(db3, {
|
|
51826
51959
|
roomId,
|
|
@@ -51861,6 +51994,10 @@ async function executeQueenTool(db3, roomId, workerId, toolName, args2) {
|
|
|
51861
51994
|
const systemPrompt = String(args2.systemPrompt ?? args2.system_prompt ?? args2.instructions ?? args2.prompt ?? "").trim();
|
|
51862
51995
|
if (!name) return { content: 'Error: name is required for quoroom_create_worker. Provide a "name" string.', isError: true };
|
|
51863
51996
|
if (!systemPrompt) return { content: `Error: systemPrompt is required for quoroom_create_worker. Provide a "systemPrompt" string describing this worker's role and instructions.`, isError: true };
|
|
51997
|
+
const existingWorkers = listRoomWorkers(db3, roomId);
|
|
51998
|
+
if (existingWorkers.some((w) => w.name.toLowerCase() === name.toLowerCase())) {
|
|
51999
|
+
return { content: `Worker "${name}" already exists in this room. Use quoroom_update_worker to modify it, or choose a different name.`, isError: true };
|
|
52000
|
+
}
|
|
51864
52001
|
const role = args2.role && args2.role !== args2.name ? String(args2.role) : void 0;
|
|
51865
52002
|
const description = args2.description ? String(args2.description) : void 0;
|
|
51866
52003
|
const preset = role ? WORKER_ROLE_PRESETS[role] : void 0;
|
|
@@ -51893,6 +52030,10 @@ async function executeQueenTool(db3, roomId, workerId, toolName, args2) {
|
|
|
51893
52030
|
const taskWorkerId = args2.workerId ? Number(args2.workerId) : void 0;
|
|
51894
52031
|
const maxTurns = args2.maxTurns ? Number(args2.maxTurns) : void 0;
|
|
51895
52032
|
const triggerType = cronExpression ? "cron" : scheduledAt ? "once" : "manual";
|
|
52033
|
+
const existingTasks = listTasks(db3, roomId, "active");
|
|
52034
|
+
if (existingTasks.some((t) => t.name.toLowerCase() === name.toLowerCase())) {
|
|
52035
|
+
return { content: `Task "${name}" already exists. Choose a different name or manage the existing task.`, isError: true };
|
|
52036
|
+
}
|
|
51896
52037
|
if (taskWorkerId) {
|
|
51897
52038
|
const taskWorker = getWorker(db3, taskWorkerId);
|
|
51898
52039
|
if (!taskWorker || taskWorker.roomId !== roomId) {
|
|
@@ -51918,6 +52059,11 @@ async function executeQueenTool(db3, roomId, workerId, toolName, args2) {
|
|
|
51918
52059
|
const name = String(args2.name ?? "");
|
|
51919
52060
|
const content = String(args2.content ?? "");
|
|
51920
52061
|
const type = String(args2.type ?? "fact");
|
|
52062
|
+
const existing = listEntities(db3, roomId).find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
52063
|
+
if (existing) {
|
|
52064
|
+
addObservation(db3, existing.id, content, "queen");
|
|
52065
|
+
return { content: `Updated memory "${name}" (added new observation to existing entry).` };
|
|
52066
|
+
}
|
|
51921
52067
|
const entity = createEntity(db3, name, type, void 0, roomId);
|
|
51922
52068
|
addObservation(db3, entity.id, content, "queen");
|
|
51923
52069
|
return { content: `Remembered "${name}".` };
|
|
@@ -51972,6 +52118,26 @@ async function executeQueenTool(db3, roomId, workerId, toolName, args2) {
|
|
|
51972
52118
|
if (!url) return { content: "Error: url is required", isError: true };
|
|
51973
52119
|
return { content: await browserAction(url, actions) };
|
|
51974
52120
|
}
|
|
52121
|
+
// ── Wallet ────────────────────────────────────────────────────
|
|
52122
|
+
case "quoroom_wallet_balance": {
|
|
52123
|
+
const wallet = getWalletByRoom(db3, roomId);
|
|
52124
|
+
if (!wallet) return { content: "No wallet found for this room.", isError: true };
|
|
52125
|
+
const summary = getWalletTransactionSummary(db3, wallet.id);
|
|
52126
|
+
const net = (parseFloat(summary.received) - parseFloat(summary.sent)).toFixed(2);
|
|
52127
|
+
return { content: `Wallet ${wallet.address}: ${net} USDC (received: ${summary.received}, sent: ${summary.sent})` };
|
|
52128
|
+
}
|
|
52129
|
+
case "quoroom_wallet_send": {
|
|
52130
|
+
return { content: "Wallet send requires on-chain transaction \u2014 use the MCP tool quoroom_wallet_send with encryptionKey, or ask the keeper to send funds.", isError: true };
|
|
52131
|
+
}
|
|
52132
|
+
case "quoroom_wallet_history": {
|
|
52133
|
+
const wallet = getWalletByRoom(db3, roomId);
|
|
52134
|
+
if (!wallet) return { content: "No wallet found for this room.", isError: true };
|
|
52135
|
+
const limit = Math.min(Number(args2.limit) || 10, 50);
|
|
52136
|
+
const txs = listWalletTransactions(db3, wallet.id, limit);
|
|
52137
|
+
if (txs.length === 0) return { content: "No transactions yet." };
|
|
52138
|
+
const lines = txs.map((tx) => `[${tx.type}] ${tx.amount} USDC \u2014 ${tx.description ?? ""} (${tx.status})`).join("\n");
|
|
52139
|
+
return { content: lines };
|
|
52140
|
+
}
|
|
51975
52141
|
default:
|
|
51976
52142
|
return { content: `Unknown tool: ${toolName}`, isError: true };
|
|
51977
52143
|
}
|
|
@@ -52334,6 +52500,47 @@ var init_queen_tools = __esm({
|
|
|
52334
52500
|
required: ["url", "actions"]
|
|
52335
52501
|
}
|
|
52336
52502
|
}
|
|
52503
|
+
},
|
|
52504
|
+
// ── Wallet ──────────────────────────────────────────────────────────────
|
|
52505
|
+
{
|
|
52506
|
+
type: "function",
|
|
52507
|
+
function: {
|
|
52508
|
+
name: "quoroom_wallet_balance",
|
|
52509
|
+
description: "Get the room's wallet balance (USDC). Returns address and transaction summary.",
|
|
52510
|
+
parameters: {
|
|
52511
|
+
type: "object",
|
|
52512
|
+
properties: {},
|
|
52513
|
+
required: []
|
|
52514
|
+
}
|
|
52515
|
+
}
|
|
52516
|
+
},
|
|
52517
|
+
{
|
|
52518
|
+
type: "function",
|
|
52519
|
+
function: {
|
|
52520
|
+
name: "quoroom_wallet_send",
|
|
52521
|
+
description: "Send USDC from the room's wallet to an address.",
|
|
52522
|
+
parameters: {
|
|
52523
|
+
type: "object",
|
|
52524
|
+
properties: {
|
|
52525
|
+
to: { type: "string", description: "Recipient address (0x...)" },
|
|
52526
|
+
amount: { type: "string", description: 'Amount (e.g., "10.50")' }
|
|
52527
|
+
},
|
|
52528
|
+
required: ["to", "amount"]
|
|
52529
|
+
}
|
|
52530
|
+
}
|
|
52531
|
+
},
|
|
52532
|
+
{
|
|
52533
|
+
type: "function",
|
|
52534
|
+
function: {
|
|
52535
|
+
name: "quoroom_wallet_history",
|
|
52536
|
+
description: "Get recent wallet transaction history.",
|
|
52537
|
+
parameters: {
|
|
52538
|
+
type: "object",
|
|
52539
|
+
properties: {
|
|
52540
|
+
limit: { type: "string", description: "Max transactions to return (default: 10)" }
|
|
52541
|
+
}
|
|
52542
|
+
}
|
|
52543
|
+
}
|
|
52337
52544
|
}
|
|
52338
52545
|
];
|
|
52339
52546
|
}
|
|
@@ -52713,6 +52920,14 @@ ${roomTasks.map(
|
|
|
52713
52920
|
(t) => `- #${t.id} "${t.name}" [${t.triggerType}] \u2014 ${t.status}`
|
|
52714
52921
|
).join("\n")}`);
|
|
52715
52922
|
}
|
|
52923
|
+
const wallet = getWalletByRoom(db3, roomId);
|
|
52924
|
+
if (wallet) {
|
|
52925
|
+
const summary = getWalletTransactionSummary(db3, wallet.id);
|
|
52926
|
+
const net = (parseFloat(summary.received) - parseFloat(summary.sent)).toFixed(2);
|
|
52927
|
+
contextParts.push(`## Wallet
|
|
52928
|
+
Address: ${wallet.address}
|
|
52929
|
+
Balance: ${net} USDC (received: ${summary.received}, spent: ${summary.sent})`);
|
|
52930
|
+
}
|
|
52716
52931
|
if (unreadMessages.length > 0) {
|
|
52717
52932
|
contextParts.push(`## Unread Messages
|
|
52718
52933
|
${unreadMessages.map(
|
|
@@ -52750,19 +52965,54 @@ ${top3.map(
|
|
|
52750
52965
|
}
|
|
52751
52966
|
contextParts.push(`## Execution Settings
|
|
52752
52967
|
${settingsParts.join("\n")}`);
|
|
52968
|
+
const STUCK_THRESHOLD_CYCLES = 2;
|
|
52969
|
+
const productiveCallCount = countProductiveToolCalls(db3, worker.id, STUCK_THRESHOLD_CYCLES);
|
|
52970
|
+
const recentCompletedCycles = listRoomCycles(db3, roomId, 5).filter((c) => c.workerId === worker.id && c.status === "completed");
|
|
52971
|
+
const isStuck = recentCompletedCycles.length >= STUCK_THRESHOLD_CYCLES && productiveCallCount === 0;
|
|
52972
|
+
if (isStuck) {
|
|
52973
|
+
contextParts.push(`## \u26A0 STUCK DETECTED
|
|
52974
|
+
Your last ${STUCK_THRESHOLD_CYCLES} cycles produced no external results (no web searches, no memories stored, no goal progress, no keeper messages). You MUST change strategy NOW:
|
|
52975
|
+
- Try a different web search query
|
|
52976
|
+
- Store what you know in memory even if incomplete
|
|
52977
|
+
- Update goal progress with what you've learned
|
|
52978
|
+
- Message the keeper if you're blocked
|
|
52979
|
+
Do NOT repeat the same approach. Pivot immediately.`);
|
|
52980
|
+
logBuffer.addSynthetic("system", `Stuck detector: 0 productive tool calls in last ${STUCK_THRESHOLD_CYCLES} cycles \u2014 injecting pivot directive`);
|
|
52981
|
+
}
|
|
52753
52982
|
const selfRegulateHint = rateLimitEvents.length > 0 ? "\n- **Self-regulate**: You are hitting rate limits. Use quoroom_configure_room to increase your cycle gap or reduce max turns to stay within API limits." : "";
|
|
52754
52983
|
const isClaude = model === "claude" || model.startsWith("claude-");
|
|
52755
52984
|
const toolCallInstruction = isClaude ? "Always call tools to take action \u2014 do not just describe what you would do." : "IMPORTANT: You MUST call at least one tool in your response. Respond ONLY with a tool call \u2014 do not write explanatory text without a tool call.";
|
|
52756
|
-
const
|
|
52757
|
-
const
|
|
52758
|
-
const
|
|
52759
|
-
|
|
52760
|
-
|
|
52761
|
-
|
|
52762
|
-
|
|
52763
|
-
|
|
52764
|
-
|
|
52765
|
-
|
|
52985
|
+
const allowListRaw = status2.room.allowedTools?.trim() || null;
|
|
52986
|
+
const allowSet = allowListRaw ? new Set(allowListRaw.split(",").map((s) => s.trim())) : null;
|
|
52987
|
+
const has = (name) => !allowSet || allowSet.has(name);
|
|
52988
|
+
const toolLines = [];
|
|
52989
|
+
const goalTools = ["quoroom_set_goal", "quoroom_update_progress", "quoroom_create_subgoal", "quoroom_complete_goal", "quoroom_abandon_goal"].filter(has);
|
|
52990
|
+
if (goalTools.length) toolLines.push(`**Goals:** ${goalTools.join(", ")}`);
|
|
52991
|
+
const govTools = ["quoroom_propose", "quoroom_vote"].filter(has);
|
|
52992
|
+
if (govTools.length) toolLines.push(`**Governance:** ${govTools.join(", ")}`);
|
|
52993
|
+
const workerTools = ["quoroom_create_worker", "quoroom_update_worker"].filter(has);
|
|
52994
|
+
if (workerTools.length) toolLines.push(`**Workers:** ${workerTools.join(", ")}`);
|
|
52995
|
+
if (has("quoroom_schedule")) toolLines.push("**Tasks:** quoroom_schedule");
|
|
52996
|
+
const memTools = ["quoroom_remember", "quoroom_recall"].filter(has);
|
|
52997
|
+
if (memTools.length) toolLines.push(`**Memory:** ${memTools.join(", ")}`);
|
|
52998
|
+
const walletToolNames = isCli ? ["quoroom_wallet_balance", "quoroom_wallet_send", "quoroom_wallet_history", "quoroom_wallet_topup"] : ["quoroom_wallet_balance", "quoroom_wallet_send", "quoroom_wallet_history"];
|
|
52999
|
+
const filteredWallet = walletToolNames.filter(has);
|
|
53000
|
+
if (filteredWallet.length) toolLines.push(`**Wallet:** ${filteredWallet.join(", ")}`);
|
|
53001
|
+
const webToolNames = isCli ? null : ["quoroom_web_search", "quoroom_web_fetch", "quoroom_browser"];
|
|
53002
|
+
if (isCli) {
|
|
53003
|
+
if (has("quoroom_web_search") || has("quoroom_web_fetch")) toolLines.push("**Web:** (use your built-in web search and fetch tools)");
|
|
53004
|
+
} else {
|
|
53005
|
+
const filteredWeb = (webToolNames || []).filter(has);
|
|
53006
|
+
if (filteredWeb.length) toolLines.push(`**Web:** ${filteredWeb.join(", ")}`);
|
|
53007
|
+
}
|
|
53008
|
+
const commsToolNames = isCli ? ["quoroom_inbox_send_keeper", "quoroom_inbox_list", "quoroom_inbox_send_room", "quoroom_inbox_reply"] : ["quoroom_ask_keeper"];
|
|
53009
|
+
const filteredComms = commsToolNames.filter(has);
|
|
53010
|
+
if (filteredComms.length) {
|
|
53011
|
+
if (isCli) toolLines.push(`**Comms:** ${filteredComms.map((t) => t === "quoroom_inbox_send_keeper" ? `${t} (message keeper)` : t === "quoroom_inbox_list" ? `${t} (inter-room)` : t).join(", ")}`);
|
|
53012
|
+
else toolLines.push(`**Comms:** ${filteredComms.join(", ")}`);
|
|
53013
|
+
}
|
|
53014
|
+
if (has("quoroom_configure_room")) toolLines.push(`**Settings:** quoroom_configure_room${selfRegulateHint}`);
|
|
53015
|
+
const toolList = toolLines.join("\n");
|
|
52766
53016
|
const sendKeeperTool = isCli ? "quoroom_inbox_send_keeper" : "quoroom_ask_keeper";
|
|
52767
53017
|
contextParts.push(`## Instructions
|
|
52768
53018
|
Based on the current state, decide what to do next and call the appropriate tools. Available tools:
|
|
@@ -52771,6 +53021,8 @@ ${toolList}
|
|
|
52771
53021
|
|
|
52772
53022
|
Do NOT "stand by" or wait for anyone \u2014 every cycle must make progress. Act autonomously: make decisions and execute. Inform the keeper of progress or important updates using ${sendKeeperTool}, but never block on a response. If the keeper hasn't replied, proceed with your best judgment.
|
|
52773
53023
|
|
|
53024
|
+
Revenue is always a priority. Every room must sustain itself financially. Actively seek ways to earn: offer services to the keeper, propose paid work to other rooms, or find monetizable opportunities in your domain. Check your wallet balance and report financial status to the keeper.
|
|
53025
|
+
|
|
52774
53026
|
${toolCallInstruction}`);
|
|
52775
53027
|
const prompt = contextParts.join("\n\n");
|
|
52776
53028
|
updateAgentState(db3, worker.id, "acting");
|
|
@@ -52779,8 +53031,9 @@ ${toolCallInstruction}`);
|
|
|
52779
53031
|
logBuffer.flush();
|
|
52780
53032
|
const apiKey = apiKeyEarly;
|
|
52781
53033
|
const needsQueenTools = model === "openai" || model.startsWith("openai:") || model === "anthropic" || model.startsWith("anthropic:") || model.startsWith("claude-api:");
|
|
53034
|
+
const filteredToolDefs = allowSet ? QUEEN_TOOL_DEFINITIONS.filter((t) => allowSet.has(t.function.name)) : QUEEN_TOOL_DEFINITIONS;
|
|
52782
53035
|
const apiToolOpts = needsQueenTools ? {
|
|
52783
|
-
toolDefs:
|
|
53036
|
+
toolDefs: filteredToolDefs,
|
|
52784
53037
|
onToolCall: async (toolName, args2) => {
|
|
52785
53038
|
logBuffer.addSynthetic("tool_call", `\u2192 ${toolName}(${JSON.stringify(args2)})`);
|
|
52786
53039
|
const result2 = await executeQueenTool(db3, roomId, worker.id, toolName, args2);
|
|
@@ -52796,6 +53049,8 @@ ${toolCallInstruction}`);
|
|
|
52796
53049
|
timeoutMs: 5 * 60 * 1e3,
|
|
52797
53050
|
maxTurns: maxTurns ?? 10,
|
|
52798
53051
|
onConsoleLog: logBuffer.onConsoleLog,
|
|
53052
|
+
// CLI models: block non-quoroom MCP tools (daymon, etc.)
|
|
53053
|
+
disallowedTools: isCli ? "mcp__daymon*" : void 0,
|
|
52799
53054
|
// CLI models: pass resumeSessionId for native --resume
|
|
52800
53055
|
resumeSessionId,
|
|
52801
53056
|
// API models: pass conversation history + persistence callback
|
|
@@ -52810,6 +53065,23 @@ ${toolCallInstruction}`);
|
|
|
52810
53065
|
if (rateLimitInfo) {
|
|
52811
53066
|
throw new RateLimitError(rateLimitInfo);
|
|
52812
53067
|
}
|
|
53068
|
+
if (result.exitCode !== 0) {
|
|
53069
|
+
const errorDetail = result.output?.trim() || `exit code ${result.exitCode}`;
|
|
53070
|
+
logBuffer.addSynthetic("error", `Agent execution failed: ${errorDetail.slice(0, 500)}`);
|
|
53071
|
+
logBuffer.flush();
|
|
53072
|
+
completeWorkerCycle(db3, cycle.id, errorDetail.slice(0, 500), result.usage);
|
|
53073
|
+
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
53074
|
+
logRoomActivity(
|
|
53075
|
+
db3,
|
|
53076
|
+
roomId,
|
|
53077
|
+
"error",
|
|
53078
|
+
`Agent cycle failed (${worker.name}): ${errorDetail.slice(0, 200)}`,
|
|
53079
|
+
errorDetail,
|
|
53080
|
+
worker.id
|
|
53081
|
+
);
|
|
53082
|
+
updateAgentState(db3, worker.id, "idle");
|
|
53083
|
+
return result.output;
|
|
53084
|
+
}
|
|
52813
53085
|
if (isCli && result.sessionId) {
|
|
52814
53086
|
saveAgentSession(db3, worker.id, { sessionId: result.sessionId, model });
|
|
52815
53087
|
}
|
|
@@ -52887,7 +53159,7 @@ var require_package = __commonJS({
|
|
|
52887
53159
|
"package.json"(exports2, module2) {
|
|
52888
53160
|
module2.exports = {
|
|
52889
53161
|
name: "quoroom",
|
|
52890
|
-
version: "0.1.
|
|
53162
|
+
version: "0.1.22",
|
|
52891
53163
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
52892
53164
|
main: "./out/mcp/server.js",
|
|
52893
53165
|
bin: {
|
|
@@ -52912,7 +53184,7 @@ var require_package = __commonJS({
|
|
|
52912
53184
|
"build:mcp": "node scripts/build-mcp.js",
|
|
52913
53185
|
"build:ui": "vite build --config src/ui/vite.config.ts",
|
|
52914
53186
|
"kill:ports": "node scripts/kill-ports.js",
|
|
52915
|
-
"kill:dev-ports": "npm run kill:ports -- 4700
|
|
53187
|
+
"kill:dev-ports": "npm run kill:ports -- 4700 3715 5173",
|
|
52916
53188
|
"dev:links": "node scripts/dev-links.js",
|
|
52917
53189
|
dev: `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
|
|
52918
53190
|
"dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
@@ -52920,7 +53192,7 @@ var require_package = __commonJS({
|
|
|
52920
53192
|
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
52921
53193
|
"doctor:split": "node scripts/doctor-split.js",
|
|
52922
53194
|
"dev:isolated": `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'`,
|
|
52923
|
-
"dev:cloud": `sh -c 'npm run kill:ports --
|
|
53195
|
+
"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'`,
|
|
52924
53196
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
52925
53197
|
"seed:style-demo": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev; node scripts/seed-style-demo.js'",
|
|
52926
53198
|
typecheck: "tsc --noEmit",
|
|
@@ -53345,6 +53617,7 @@ function registerRoomRoutes(router) {
|
|
|
53345
53617
|
const trimmed = body.queenNickname.trim().replace(/\s+/g, "");
|
|
53346
53618
|
if (trimmed.length > 0 && trimmed.length <= 40) updates.queenNickname = trimmed;
|
|
53347
53619
|
}
|
|
53620
|
+
if (body.allowedTools !== void 0) updates.allowedTools = body.allowedTools || null;
|
|
53348
53621
|
if (body.config !== void 0 && typeof body.config === "object" && body.config !== null) {
|
|
53349
53622
|
updates.config = { ...room.config, ...body.config };
|
|
53350
53623
|
}
|
|
@@ -55344,7 +55617,15 @@ function registerChatRoutes(router) {
|
|
|
55344
55617
|
// 3 minutes
|
|
55345
55618
|
});
|
|
55346
55619
|
if (result.exitCode !== 0 || result.timedOut) {
|
|
55347
|
-
const
|
|
55620
|
+
const rawOutput = result.output?.trim();
|
|
55621
|
+
let reason;
|
|
55622
|
+
if (result.timedOut) {
|
|
55623
|
+
reason = "Chat request timed out";
|
|
55624
|
+
} else if (rawOutput) {
|
|
55625
|
+
reason = rawOutput;
|
|
55626
|
+
} else {
|
|
55627
|
+
reason = `Chat execution failed (model: ${model}, exit code: ${result.exitCode})`;
|
|
55628
|
+
}
|
|
55348
55629
|
return { status: result.timedOut ? 504 : 502, error: reason.slice(0, 500) };
|
|
55349
55630
|
}
|
|
55350
55631
|
const response = result.output || "No response";
|
|
@@ -55558,7 +55839,7 @@ function semverGt(a, b) {
|
|
|
55558
55839
|
}
|
|
55559
55840
|
function getCurrentVersion() {
|
|
55560
55841
|
try {
|
|
55561
|
-
return true ? "0.1.
|
|
55842
|
+
return true ? "0.1.22" : null.version;
|
|
55562
55843
|
} catch {
|
|
55563
55844
|
return "0.0.0";
|
|
55564
55845
|
}
|
|
@@ -55741,7 +56022,7 @@ var init_updateChecker = __esm({
|
|
|
55741
56022
|
function getVersion3() {
|
|
55742
56023
|
if (cachedVersion) return cachedVersion;
|
|
55743
56024
|
try {
|
|
55744
|
-
cachedVersion = true ? "0.1.
|
|
56025
|
+
cachedVersion = true ? "0.1.22" : null.version;
|
|
55745
56026
|
} catch {
|
|
55746
56027
|
cachedVersion = "unknown";
|
|
55747
56028
|
}
|
|
@@ -62056,7 +62337,7 @@ __export(update_exports, {
|
|
|
62056
62337
|
});
|
|
62057
62338
|
function getCurrentVersion2() {
|
|
62058
62339
|
try {
|
|
62059
|
-
return true ? "0.1.
|
|
62340
|
+
return true ? "0.1.22" : null.version;
|
|
62060
62341
|
} catch {
|
|
62061
62342
|
return "0.0.0";
|
|
62062
62343
|
}
|
package/out/mcp/server.js
CHANGED
|
@@ -31163,6 +31163,7 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|
|
31163
31163
|
queen_nickname TEXT,
|
|
31164
31164
|
chat_session_id TEXT,
|
|
31165
31165
|
referred_by_code TEXT,
|
|
31166
|
+
allowed_tools TEXT,
|
|
31166
31167
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
31167
31168
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
31168
31169
|
);
|
|
@@ -32357,6 +32358,7 @@ function mapRoomRow(row) {
|
|
|
32357
32358
|
queenNickname: row.queen_nickname ?? null,
|
|
32358
32359
|
chatSessionId: row.chat_session_id ?? null,
|
|
32359
32360
|
referredByCode: row.referred_by_code ?? null,
|
|
32361
|
+
allowedTools: row.allowed_tools ?? null,
|
|
32360
32362
|
webhookToken: row.webhook_token ?? null,
|
|
32361
32363
|
createdAt: row.created_at,
|
|
32362
32364
|
updatedAt: row.updated_at
|
|
@@ -32445,6 +32447,7 @@ function updateRoom(db2, id, updates) {
|
|
|
32445
32447
|
config: "config",
|
|
32446
32448
|
referredByCode: "referred_by_code",
|
|
32447
32449
|
queenNickname: "queen_nickname",
|
|
32450
|
+
allowedTools: "allowed_tools",
|
|
32448
32451
|
webhookToken: "webhook_token"
|
|
32449
32452
|
};
|
|
32450
32453
|
const fields = [];
|
|
@@ -33002,6 +33005,13 @@ function runMigrations(database, log = console.log) {
|
|
|
33002
33005
|
database.exec(`ALTER TABLE workers ADD COLUMN max_turns INTEGER`);
|
|
33003
33006
|
log("Migrated: added cycle_gap_ms and max_turns columns to workers");
|
|
33004
33007
|
}
|
|
33008
|
+
const hasRoomAllowedTools = database.prepare(
|
|
33009
|
+
`SELECT name FROM pragma_table_info('rooms') WHERE name='allowed_tools'`
|
|
33010
|
+
).get()?.name;
|
|
33011
|
+
if (!hasRoomAllowedTools) {
|
|
33012
|
+
database.exec(`ALTER TABLE rooms ADD COLUMN allowed_tools TEXT`);
|
|
33013
|
+
log("Migrated: added allowed_tools column to rooms");
|
|
33014
|
+
}
|
|
33005
33015
|
const ollamaWorkers = database.prepare(`SELECT id FROM workers WHERE model LIKE 'ollama:%'`).all();
|
|
33006
33016
|
if (ollamaWorkers.length > 0) {
|
|
33007
33017
|
database.prepare(`UPDATE workers SET model = 'claude' WHERE model LIKE 'ollama:%'`).run();
|
|
@@ -47485,7 +47495,7 @@ Share this with the keeper or potential collaborators. Rooms created through thi
|
|
|
47485
47495
|
async function main() {
|
|
47486
47496
|
const server = new McpServer({
|
|
47487
47497
|
name: "quoroom",
|
|
47488
|
-
version: true ? "0.1.
|
|
47498
|
+
version: true ? "0.1.22" : "0.0.0"
|
|
47489
47499
|
});
|
|
47490
47500
|
registerMemoryTools(server);
|
|
47491
47501
|
registerSchedulerTools(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quoroom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Autonomous AI agent collective engine — Queen, Workers, Quorum",
|
|
5
5
|
"main": "./out/mcp/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"build:mcp": "node scripts/build-mcp.js",
|
|
26
26
|
"build:ui": "vite build --config src/ui/vite.config.ts",
|
|
27
27
|
"kill:ports": "node scripts/kill-ports.js",
|
|
28
|
-
"kill:dev-ports": "npm run kill:ports -- 4700
|
|
28
|
+
"kill:dev-ports": "npm run kill:ports -- 4700 3715 5173",
|
|
29
29
|
"dev:links": "node scripts/dev-links.js",
|
|
30
30
|
"dev": "sh -c 'npm run kill:dev-ports && trap \"kill 0\" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'",
|
|
31
31
|
"dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
|
|
34
34
|
"doctor:split": "node scripts/doctor-split.js",
|
|
35
35
|
"dev:isolated": "sh -c 'npm run kill:dev-ports && trap \"kill 0\" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'",
|
|
36
|
-
"dev:cloud": "sh -c 'npm run kill:ports --
|
|
36
|
+
"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'",
|
|
37
37
|
"dev:ui": "vite --config src/ui/vite.config.ts",
|
|
38
38
|
"seed:style-demo": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev; node scripts/seed-style-demo.js'",
|
|
39
39
|
"typecheck": "tsc --noEmit",
|