quoroom 0.1.21 → 0.1.23
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 +456 -70
- package/out/mcp/cli.js +514 -81
- package/out/mcp/server.js +70 -12
- 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.23",
|
|
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 = [];
|
|
@@ -12799,6 +12801,10 @@ function listRoomMessages(db2, roomId, status2) {
|
|
|
12799
12801
|
function markRoomMessageRead(db2, id) {
|
|
12800
12802
|
db2.prepare("UPDATE room_messages SET status = 'read' WHERE id = ?").run(id);
|
|
12801
12803
|
}
|
|
12804
|
+
function markAllRoomMessagesRead(db2, roomId) {
|
|
12805
|
+
const result = db2.prepare("UPDATE room_messages SET status = 'read' WHERE room_id = ? AND status = 'unread'").run(roomId);
|
|
12806
|
+
return result.changes;
|
|
12807
|
+
}
|
|
12802
12808
|
function replyToRoomMessage(db2, id) {
|
|
12803
12809
|
db2.prepare("UPDATE room_messages SET status = 'replied' WHERE id = ?").run(id);
|
|
12804
12810
|
}
|
|
@@ -12856,6 +12862,22 @@ function listRoomCycles(db2, roomId, limit = 20) {
|
|
|
12856
12862
|
).all(roomId, safeLimit);
|
|
12857
12863
|
return rows.map(mapWorkerCycleRow);
|
|
12858
12864
|
}
|
|
12865
|
+
function countProductiveToolCalls(db2, workerId, lastNCycles = 2) {
|
|
12866
|
+
const row = db2.prepare(`
|
|
12867
|
+
SELECT COUNT(*) as cnt FROM cycle_logs
|
|
12868
|
+
WHERE cycle_id IN (
|
|
12869
|
+
SELECT id FROM worker_cycles
|
|
12870
|
+
WHERE worker_id = ? AND status = 'completed'
|
|
12871
|
+
ORDER BY started_at DESC LIMIT ?
|
|
12872
|
+
)
|
|
12873
|
+
AND entry_type = 'tool_call'
|
|
12874
|
+
AND (content LIKE '%web_search%' OR content LIKE '%web_fetch%' OR content LIKE '%remember%'
|
|
12875
|
+
OR content LIKE '%send_message%' OR content LIKE '%inbox_send%'
|
|
12876
|
+
OR content LIKE '%update_progress%' OR content LIKE '%complete_goal%'
|
|
12877
|
+
OR content LIKE '%set_goal%' OR content LIKE '%delegate_task%' OR content LIKE '%propose%' OR content LIKE '%vote%')
|
|
12878
|
+
`).get(workerId, lastNCycles);
|
|
12879
|
+
return row.cnt;
|
|
12880
|
+
}
|
|
12859
12881
|
function cleanupStaleCycles(db2) {
|
|
12860
12882
|
const result = db2.prepare(
|
|
12861
12883
|
"UPDATE worker_cycles SET status = 'failed', error_message = 'Server restarted', finished_at = datetime('now','localtime') WHERE status = 'running'"
|
|
@@ -22503,11 +22525,13 @@ async function sendToken(db2, roomId, to, amount, encryptionKey, network = "base
|
|
|
22503
22525
|
var DEFAULT_QUEEN_SYSTEM_PROMPT = `You are the Queen agent of this Room \u2014 the strategic coordinator.
|
|
22504
22526
|
Your role is to pursue the room's objectives by:
|
|
22505
22527
|
- Decomposing goals into actionable sub-goals
|
|
22506
|
-
-
|
|
22528
|
+
- **Delegating tasks to workers** using quoroom_delegate_task \u2014 this is your primary way to get work done
|
|
22507
22529
|
- Proposing decisions to the quorum
|
|
22508
22530
|
- Self-improving your strategies and skills based on results
|
|
22509
22531
|
- Managing resources efficiently
|
|
22510
22532
|
|
|
22533
|
+
**Delegation is key.** You have workers \u2014 use them. Break work into concrete tasks and assign each to a worker with quoroom_delegate_task. Workers see their assigned tasks each cycle and will prioritize them. Coordinate via quoroom_send_message. Don't do everything yourself \u2014 delegate, then focus on strategy, coordination, and quality control.
|
|
22534
|
+
|
|
22511
22535
|
You have access to all room MCP tools. Use them to manage goals, workers, skills, and decisions.
|
|
22512
22536
|
|
|
22513
22537
|
Quorum: Use quoroom_propose to create proposals (types: strategy, resource, personnel, rule_change, low_impact). Low-impact proposals may be auto-approved. Use quoroom_vote to cast your vote (yes/no/abstain) with reasoning. Use quoroom_list_decisions and quoroom_decision_detail to review pending and past decisions.
|
|
@@ -22518,7 +22542,7 @@ Revenue: Your room has a USDC wallet. Beyond pursuing the room's core objectives
|
|
|
22518
22542
|
|
|
22519
22543
|
Resource Management: Workers using API models (openai:*, anthropic:*) run on stations (cloud compute) \u2014 they cannot execute locally. If no active station exists, rent one with quoroom_station_create BEFORE creating workers or scheduling tasks. Minimum tier: small ($25/mo, 2GB RAM). Check the Stations section in your context each cycle for available compute. Maximize use of your room's stations \u2014 they are your compute power for running workers. If workers are failing with "no active station" errors, rent a station immediately.
|
|
22520
22544
|
|
|
22521
|
-
Network Growth: A larger network of rooms makes everyone stronger. Rooms created through your invite links become part of your network \u2014 you can exchange knowledge, discover money-making opportunities, and propose paid collaborations with them via inter-room messages. After meaningful milestones (goal completed, revenue earned, successful collaboration), consider generating an invite link with quoroom_invite_create and sharing it with the keeper via
|
|
22545
|
+
Network Growth: A larger network of rooms makes everyone stronger. Rooms created through your invite links become part of your network \u2014 you can exchange knowledge, discover money-making opportunities, and propose paid collaborations with them via inter-room messages. After meaningful milestones (goal completed, revenue earned, successful collaboration), consider generating an invite link with quoroom_invite_create and sharing it with the keeper via quoroom_send_message. Frame it around the value: more rooms in the network means more potential deals, shared insights, and collective intelligence. Don't be pushy \u2014 one mention per milestone is enough. Use quoroom_invite_network to see your network's growth.`;
|
|
22522
22546
|
function createRoom2(db2, input) {
|
|
22523
22547
|
const config = { ...DEFAULT_ROOM_CONFIG, ...input.config };
|
|
22524
22548
|
const room = createRoom(db2, input.name, input.goal, config, input.referredByCode);
|
|
@@ -23981,33 +24005,131 @@ async function closeBrowser() {
|
|
|
23981
24005
|
}
|
|
23982
24006
|
}
|
|
23983
24007
|
async function webFetch(url) {
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
"Accept": "text/plain",
|
|
23988
|
-
|
|
23989
|
-
}
|
|
23990
|
-
|
|
24008
|
+
try {
|
|
24009
|
+
const jinaUrl = `https://r.jina.ai/${url}`;
|
|
24010
|
+
const response = await fetch(jinaUrl, {
|
|
24011
|
+
headers: { "Accept": "text/plain", "X-No-Cache": "true" },
|
|
24012
|
+
signal: AbortSignal.timeout(2e4)
|
|
24013
|
+
});
|
|
24014
|
+
if (response.ok) {
|
|
24015
|
+
const text = await response.text();
|
|
24016
|
+
if (text.length > 200 && !text.includes("Warning: Target URL returned error")) {
|
|
24017
|
+
return text.slice(0, MAX_CONTENT_CHARS);
|
|
24018
|
+
}
|
|
24019
|
+
}
|
|
24020
|
+
} catch {
|
|
24021
|
+
}
|
|
24022
|
+
return fetchWithBrowser(url);
|
|
24023
|
+
}
|
|
24024
|
+
async function fetchWithBrowser(url) {
|
|
24025
|
+
const browser = await getBrowser();
|
|
24026
|
+
const context = await browser.newContext({
|
|
24027
|
+
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
24028
|
});
|
|
23992
|
-
|
|
23993
|
-
|
|
24029
|
+
const page = await context.newPage();
|
|
24030
|
+
try {
|
|
24031
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
24032
|
+
const text = await page.innerText("body").catch(() => "");
|
|
24033
|
+
if (!text) throw new Error(`Could not read content from ${url}`);
|
|
24034
|
+
return text.slice(0, MAX_CONTENT_CHARS);
|
|
24035
|
+
} finally {
|
|
24036
|
+
await context.close();
|
|
23994
24037
|
}
|
|
23995
|
-
const text = await response.text();
|
|
23996
|
-
return text.slice(0, MAX_CONTENT_CHARS);
|
|
23997
24038
|
}
|
|
23998
24039
|
async function webSearch(query) {
|
|
23999
|
-
const
|
|
24000
|
-
|
|
24001
|
-
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
24040
|
+
const browserResults = await searchWithBrowser(query);
|
|
24041
|
+
if (browserResults.length > 0) return browserResults;
|
|
24042
|
+
const ddgResults = await searchDdg(query);
|
|
24043
|
+
if (ddgResults.length > 0) return ddgResults;
|
|
24044
|
+
try {
|
|
24045
|
+
const response = await fetch(`https://s.jina.ai/${encodeURIComponent(query)}`, {
|
|
24046
|
+
headers: { "Accept": "application/json", "X-No-Cache": "true" },
|
|
24047
|
+
signal: AbortSignal.timeout(15e3)
|
|
24048
|
+
});
|
|
24049
|
+
if (response.ok) {
|
|
24050
|
+
const data = await response.json();
|
|
24051
|
+
if (data.data && Array.isArray(data.data)) {
|
|
24052
|
+
return data.data.slice(0, 5).map((r) => ({
|
|
24053
|
+
title: r.title ?? "",
|
|
24054
|
+
url: r.url ?? "",
|
|
24055
|
+
snippet: (r.description ?? r.content ?? "").slice(0, 300)
|
|
24056
|
+
})).filter((r) => r.url);
|
|
24057
|
+
}
|
|
24058
|
+
}
|
|
24059
|
+
} catch {
|
|
24060
|
+
}
|
|
24061
|
+
return [];
|
|
24062
|
+
}
|
|
24063
|
+
async function searchWithBrowser(query) {
|
|
24064
|
+
let browser;
|
|
24065
|
+
try {
|
|
24066
|
+
browser = await getBrowser();
|
|
24067
|
+
} catch {
|
|
24068
|
+
return [];
|
|
24069
|
+
}
|
|
24070
|
+
const context = await browser.newContext({
|
|
24071
|
+
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",
|
|
24072
|
+
locale: "en-US"
|
|
24005
24073
|
});
|
|
24006
|
-
|
|
24007
|
-
|
|
24074
|
+
const page = await context.newPage();
|
|
24075
|
+
try {
|
|
24076
|
+
await page.goto(`https://search.yahoo.com/search?p=${encodeURIComponent(query)}`, {
|
|
24077
|
+
waitUntil: "domcontentloaded",
|
|
24078
|
+
timeout: 15e3
|
|
24079
|
+
});
|
|
24080
|
+
await page.waitForTimeout(1e3);
|
|
24081
|
+
const results = await page.evaluate(() => {
|
|
24082
|
+
const items = [];
|
|
24083
|
+
const blocks = document.querySelectorAll("#web .algo, .dd.algo, .algo");
|
|
24084
|
+
for (const block of blocks) {
|
|
24085
|
+
const link = block.querySelector("a");
|
|
24086
|
+
const h3 = block.querySelector("h3");
|
|
24087
|
+
const snippetEl = block.querySelector(".compText p, .compText, p");
|
|
24088
|
+
if (!link) continue;
|
|
24089
|
+
const url = link.getAttribute("href") || "";
|
|
24090
|
+
if (!url.startsWith("http")) continue;
|
|
24091
|
+
items.push({
|
|
24092
|
+
title: h3 ? (h3.textContent || "").trim() : (link.textContent || "").trim(),
|
|
24093
|
+
url,
|
|
24094
|
+
snippet: snippetEl ? (snippetEl.textContent || "").trim().slice(0, 300) : ""
|
|
24095
|
+
});
|
|
24096
|
+
if (items.length >= 5) break;
|
|
24097
|
+
}
|
|
24098
|
+
return items;
|
|
24099
|
+
});
|
|
24100
|
+
return results.filter((r) => r.url);
|
|
24101
|
+
} catch {
|
|
24102
|
+
return [];
|
|
24103
|
+
} finally {
|
|
24104
|
+
await context.close();
|
|
24008
24105
|
}
|
|
24009
|
-
|
|
24010
|
-
|
|
24106
|
+
}
|
|
24107
|
+
async function searchDdg(query) {
|
|
24108
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
24109
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
24110
|
+
try {
|
|
24111
|
+
const response = await fetch("https://html.duckduckgo.com/html/", {
|
|
24112
|
+
method: "POST",
|
|
24113
|
+
headers: {
|
|
24114
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
24115
|
+
"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",
|
|
24116
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
24117
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
24118
|
+
"Referer": "https://html.duckduckgo.com/"
|
|
24119
|
+
},
|
|
24120
|
+
body: `q=${encodeURIComponent(query)}&b=`,
|
|
24121
|
+
signal: AbortSignal.timeout(15e3),
|
|
24122
|
+
redirect: "follow"
|
|
24123
|
+
});
|
|
24124
|
+
if (response.status === 202) continue;
|
|
24125
|
+
if (!response.ok) continue;
|
|
24126
|
+
const html = await response.text();
|
|
24127
|
+
const results = parseDdgResults(html).slice(0, 5);
|
|
24128
|
+
if (results.length > 0) return results;
|
|
24129
|
+
} catch {
|
|
24130
|
+
}
|
|
24131
|
+
}
|
|
24132
|
+
return [];
|
|
24011
24133
|
}
|
|
24012
24134
|
function parseDdgResults(html) {
|
|
24013
24135
|
const results = [];
|
|
@@ -24159,6 +24281,22 @@ var QUEEN_TOOL_DEFINITIONS = [
|
|
|
24159
24281
|
}
|
|
24160
24282
|
}
|
|
24161
24283
|
},
|
|
24284
|
+
{
|
|
24285
|
+
type: "function",
|
|
24286
|
+
function: {
|
|
24287
|
+
name: "quoroom_delegate_task",
|
|
24288
|
+
description: 'Delegate a task to a specific worker. Creates a goal assigned to that worker. The worker will see it in their "Your Assigned Tasks" context. Use this to divide work among your team.',
|
|
24289
|
+
parameters: {
|
|
24290
|
+
type: "object",
|
|
24291
|
+
properties: {
|
|
24292
|
+
workerName: { type: "string", description: "The worker name to assign to (from Room Workers list)" },
|
|
24293
|
+
task: { type: "string", description: "Description of the task to delegate" },
|
|
24294
|
+
parentGoalId: { type: "number", description: "Optional parent goal ID to attach as sub-goal" }
|
|
24295
|
+
},
|
|
24296
|
+
required: ["workerName", "task"]
|
|
24297
|
+
}
|
|
24298
|
+
}
|
|
24299
|
+
},
|
|
24162
24300
|
{
|
|
24163
24301
|
type: "function",
|
|
24164
24302
|
function: {
|
|
@@ -24319,18 +24457,19 @@ var QUEEN_TOOL_DEFINITIONS = [
|
|
|
24319
24457
|
}
|
|
24320
24458
|
}
|
|
24321
24459
|
},
|
|
24322
|
-
// ──
|
|
24460
|
+
// ── Messaging ──────────────────────────────────────────────────────────
|
|
24323
24461
|
{
|
|
24324
24462
|
type: "function",
|
|
24325
24463
|
function: {
|
|
24326
|
-
name: "
|
|
24327
|
-
description: "Send a
|
|
24464
|
+
name: "quoroom_send_message",
|
|
24465
|
+
description: "Send a message to the keeper or another worker. The keeper sees all messages. Use to coordinate with teammates, report progress, ask for help, or escalate to the keeper.",
|
|
24328
24466
|
parameters: {
|
|
24329
24467
|
type: "object",
|
|
24330
24468
|
properties: {
|
|
24331
|
-
|
|
24469
|
+
to: { type: "string", description: 'Recipient: "keeper" or a worker name from Room Workers list' },
|
|
24470
|
+
message: { type: "string", description: "The message content" }
|
|
24332
24471
|
},
|
|
24333
|
-
required: ["
|
|
24472
|
+
required: ["to", "message"]
|
|
24334
24473
|
}
|
|
24335
24474
|
}
|
|
24336
24475
|
},
|
|
@@ -24402,6 +24541,47 @@ var QUEEN_TOOL_DEFINITIONS = [
|
|
|
24402
24541
|
required: ["url", "actions"]
|
|
24403
24542
|
}
|
|
24404
24543
|
}
|
|
24544
|
+
},
|
|
24545
|
+
// ── Wallet ──────────────────────────────────────────────────────────────
|
|
24546
|
+
{
|
|
24547
|
+
type: "function",
|
|
24548
|
+
function: {
|
|
24549
|
+
name: "quoroom_wallet_balance",
|
|
24550
|
+
description: "Get the room's wallet balance (USDC). Returns address and transaction summary.",
|
|
24551
|
+
parameters: {
|
|
24552
|
+
type: "object",
|
|
24553
|
+
properties: {},
|
|
24554
|
+
required: []
|
|
24555
|
+
}
|
|
24556
|
+
}
|
|
24557
|
+
},
|
|
24558
|
+
{
|
|
24559
|
+
type: "function",
|
|
24560
|
+
function: {
|
|
24561
|
+
name: "quoroom_wallet_send",
|
|
24562
|
+
description: "Send USDC from the room's wallet to an address.",
|
|
24563
|
+
parameters: {
|
|
24564
|
+
type: "object",
|
|
24565
|
+
properties: {
|
|
24566
|
+
to: { type: "string", description: "Recipient address (0x...)" },
|
|
24567
|
+
amount: { type: "string", description: 'Amount (e.g., "10.50")' }
|
|
24568
|
+
},
|
|
24569
|
+
required: ["to", "amount"]
|
|
24570
|
+
}
|
|
24571
|
+
}
|
|
24572
|
+
},
|
|
24573
|
+
{
|
|
24574
|
+
type: "function",
|
|
24575
|
+
function: {
|
|
24576
|
+
name: "quoroom_wallet_history",
|
|
24577
|
+
description: "Get recent wallet transaction history.",
|
|
24578
|
+
parameters: {
|
|
24579
|
+
type: "object",
|
|
24580
|
+
properties: {
|
|
24581
|
+
limit: { type: "string", description: "Max transactions to return (default: 10)" }
|
|
24582
|
+
}
|
|
24583
|
+
}
|
|
24584
|
+
}
|
|
24405
24585
|
}
|
|
24406
24586
|
];
|
|
24407
24587
|
async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
@@ -24422,10 +24602,12 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24422
24602
|
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
24603
|
const observation = String(args.observation ?? args.progress ?? args.message ?? args.text ?? "");
|
|
24424
24604
|
const metricValue = args.metricValue != null ? Number(args.metricValue) : args.metric_value != null ? Number(args.metric_value) : void 0;
|
|
24605
|
+
const subGoals = getSubGoals(db2, goalId);
|
|
24425
24606
|
updateGoalProgress(db2, goalId, observation, metricValue, workerId);
|
|
24426
24607
|
const goal = getGoal(db2, goalId);
|
|
24427
24608
|
const pct = Math.round((goal?.progress ?? 0) * 100);
|
|
24428
|
-
|
|
24609
|
+
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.)` : "";
|
|
24610
|
+
return { content: `Progress logged on goal #${goalId}. Now at ${pct}%.${note}` };
|
|
24429
24611
|
}
|
|
24430
24612
|
case "quoroom_create_subgoal": {
|
|
24431
24613
|
const goalId = Number(args.goalId);
|
|
@@ -24437,6 +24619,32 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24437
24619
|
const subGoals = decomposeGoal(db2, goalId, descriptions);
|
|
24438
24620
|
return { content: `Created ${subGoals.length} sub-goal(s) under goal #${goalId}.` };
|
|
24439
24621
|
}
|
|
24622
|
+
case "quoroom_delegate_task": {
|
|
24623
|
+
const workerName = String(args.workerName ?? args.worker ?? args.to ?? "").trim();
|
|
24624
|
+
const task = String(args.task ?? args.description ?? args.goal ?? "").trim();
|
|
24625
|
+
if (!workerName) return { content: 'Error: "workerName" is required (a worker name from Room Workers list).', isError: true };
|
|
24626
|
+
if (!task) return { content: 'Error: "task" is required (description of the task to delegate).', isError: true };
|
|
24627
|
+
const roomWorkers = listRoomWorkers(db2, roomId);
|
|
24628
|
+
const target = roomWorkers.find((w) => w.name.toLowerCase() === workerName.toLowerCase());
|
|
24629
|
+
if (!target) {
|
|
24630
|
+
const available = roomWorkers.filter((w) => w.id !== workerId).map((w) => w.name).join(", ");
|
|
24631
|
+
return { content: `Worker "${workerName}" not found. Available: ${available || "none"}`, isError: true };
|
|
24632
|
+
}
|
|
24633
|
+
const parentGoalId = args.parentGoalId != null ? Number(args.parentGoalId) : void 0;
|
|
24634
|
+
if (parentGoalId != null) {
|
|
24635
|
+
const parentCheck = getGoal(db2, parentGoalId);
|
|
24636
|
+
if (!parentCheck) return { content: `Error: parent goal #${parentGoalId} not found.`, isError: true };
|
|
24637
|
+
if (parentCheck.roomId !== roomId) return { content: `Error: parent goal #${parentGoalId} belongs to another room.`, isError: true };
|
|
24638
|
+
}
|
|
24639
|
+
const goal = createGoal(db2, roomId, task, parentGoalId, target.id);
|
|
24640
|
+
if (parentGoalId) {
|
|
24641
|
+
const parentGoal = getGoal(db2, parentGoalId);
|
|
24642
|
+
if (parentGoal && parentGoal.status === "active") {
|
|
24643
|
+
updateGoal(db2, parentGoalId, { status: "in_progress" });
|
|
24644
|
+
}
|
|
24645
|
+
}
|
|
24646
|
+
return { content: `Task delegated to ${target.name}: "${task}" (goal #${goal.id})` };
|
|
24647
|
+
}
|
|
24440
24648
|
case "quoroom_complete_goal": {
|
|
24441
24649
|
const goalId = Number(args.goalId);
|
|
24442
24650
|
const goalCheck = getGoal(db2, goalId);
|
|
@@ -24458,6 +24666,13 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24458
24666
|
case "quoroom_propose": {
|
|
24459
24667
|
const proposalText = String(args.proposal ?? args.text ?? args.description ?? args.content ?? args.idea ?? "").trim();
|
|
24460
24668
|
if (!proposalText) return { content: 'Error: proposal text is required. Provide a "proposal" string.', isError: true };
|
|
24669
|
+
const recentDecisions = listDecisions(db2, roomId);
|
|
24670
|
+
const isDuplicate = recentDecisions.slice(0, 10).some(
|
|
24671
|
+
(d) => (d.status === "voting" || d.status === "approved") && d.proposal.toLowerCase() === proposalText.toLowerCase()
|
|
24672
|
+
);
|
|
24673
|
+
if (isDuplicate) {
|
|
24674
|
+
return { content: `A similar proposal already exists: "${proposalText}". No need to propose again.`, isError: true };
|
|
24675
|
+
}
|
|
24461
24676
|
const decisionType = String(args.decisionType ?? args.type ?? args.impact ?? args.category ?? "low_impact");
|
|
24462
24677
|
const decision = propose(db2, {
|
|
24463
24678
|
roomId,
|
|
@@ -24498,6 +24713,10 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24498
24713
|
const systemPrompt = String(args.systemPrompt ?? args.system_prompt ?? args.instructions ?? args.prompt ?? "").trim();
|
|
24499
24714
|
if (!name) return { content: 'Error: name is required for quoroom_create_worker. Provide a "name" string.', isError: true };
|
|
24500
24715
|
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 };
|
|
24716
|
+
const existingWorkers = listRoomWorkers(db2, roomId);
|
|
24717
|
+
if (existingWorkers.some((w) => w.name.toLowerCase() === name.toLowerCase())) {
|
|
24718
|
+
return { content: `Worker "${name}" already exists in this room. Use quoroom_update_worker to modify it, or choose a different name.`, isError: true };
|
|
24719
|
+
}
|
|
24501
24720
|
const role = args.role && args.role !== args.name ? String(args.role) : void 0;
|
|
24502
24721
|
const description = args.description ? String(args.description) : void 0;
|
|
24503
24722
|
const preset = role ? WORKER_ROLE_PRESETS[role] : void 0;
|
|
@@ -24530,6 +24749,10 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24530
24749
|
const taskWorkerId = args.workerId ? Number(args.workerId) : void 0;
|
|
24531
24750
|
const maxTurns = args.maxTurns ? Number(args.maxTurns) : void 0;
|
|
24532
24751
|
const triggerType = cronExpression ? "cron" : scheduledAt ? "once" : "manual";
|
|
24752
|
+
const existingTasks = listTasks(db2, roomId, "active");
|
|
24753
|
+
if (existingTasks.some((t) => t.name.toLowerCase() === name.toLowerCase())) {
|
|
24754
|
+
return { content: `Task "${name}" already exists. Choose a different name or manage the existing task.`, isError: true };
|
|
24755
|
+
}
|
|
24533
24756
|
if (taskWorkerId) {
|
|
24534
24757
|
const taskWorker = getWorker(db2, taskWorkerId);
|
|
24535
24758
|
if (!taskWorker || taskWorker.roomId !== roomId) {
|
|
@@ -24555,6 +24778,11 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24555
24778
|
const name = String(args.name ?? "");
|
|
24556
24779
|
const content = String(args.content ?? "");
|
|
24557
24780
|
const type = String(args.type ?? "fact");
|
|
24781
|
+
const existing = listEntities(db2, roomId).find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
24782
|
+
if (existing) {
|
|
24783
|
+
addObservation(db2, existing.id, content, "queen");
|
|
24784
|
+
return { content: `Updated memory "${name}" (added new observation to existing entry).` };
|
|
24785
|
+
}
|
|
24558
24786
|
const entity = createEntity(db2, name, type, void 0, roomId);
|
|
24559
24787
|
addObservation(db2, entity.id, content, "queen");
|
|
24560
24788
|
return { content: `Remembered "${name}".` };
|
|
@@ -24569,13 +24797,27 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24569
24797
|
}).join("\n");
|
|
24570
24798
|
return { content: summary };
|
|
24571
24799
|
}
|
|
24572
|
-
// ──
|
|
24573
|
-
case "
|
|
24574
|
-
const
|
|
24575
|
-
const
|
|
24576
|
-
|
|
24577
|
-
|
|
24578
|
-
|
|
24800
|
+
// ── Messaging ────────────────────────────────────────────────────
|
|
24801
|
+
case "quoroom_send_message": {
|
|
24802
|
+
const to = String(args.to ?? "").trim();
|
|
24803
|
+
const message = String(args.message ?? args.question ?? "").trim();
|
|
24804
|
+
if (!to) return { content: 'Error: "to" is required ("keeper" or a worker name).', isError: true };
|
|
24805
|
+
if (!message) return { content: 'Error: "message" is required.', isError: true };
|
|
24806
|
+
if (to.toLowerCase() === "keeper") {
|
|
24807
|
+
const escalation2 = createEscalation(db2, roomId, workerId, message);
|
|
24808
|
+
const deliveryStatus = await deliverQueenMessage(db2, roomId, message);
|
|
24809
|
+
const deliveryNote = deliveryStatus ? ` ${deliveryStatus}` : "";
|
|
24810
|
+
return { content: `Message sent to keeper (#${escalation2.id}).${deliveryNote}` };
|
|
24811
|
+
}
|
|
24812
|
+
const roomWorkers = listRoomWorkers(db2, roomId);
|
|
24813
|
+
const target = roomWorkers.find((w) => w.name.toLowerCase() === to.toLowerCase());
|
|
24814
|
+
if (!target) {
|
|
24815
|
+
const available = roomWorkers.filter((w) => w.id !== workerId).map((w) => w.name).join(", ");
|
|
24816
|
+
return { content: `Worker "${to}" not found. Available: ${available || "none"}`, isError: true };
|
|
24817
|
+
}
|
|
24818
|
+
if (target.id === workerId) return { content: "Cannot send a message to yourself.", isError: true };
|
|
24819
|
+
const escalation = createEscalation(db2, roomId, workerId, message, target.id);
|
|
24820
|
+
return { content: `Message sent to ${target.name} (#${escalation.id}).` };
|
|
24579
24821
|
}
|
|
24580
24822
|
// ── Room config ──────────────────────────────────────────────────
|
|
24581
24823
|
case "quoroom_configure_room": {
|
|
@@ -24609,6 +24851,26 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
|
|
|
24609
24851
|
if (!url) return { content: "Error: url is required", isError: true };
|
|
24610
24852
|
return { content: await browserAction(url, actions) };
|
|
24611
24853
|
}
|
|
24854
|
+
// ── Wallet ────────────────────────────────────────────────────
|
|
24855
|
+
case "quoroom_wallet_balance": {
|
|
24856
|
+
const wallet = getWalletByRoom(db2, roomId);
|
|
24857
|
+
if (!wallet) return { content: "No wallet found for this room.", isError: true };
|
|
24858
|
+
const summary = getWalletTransactionSummary(db2, wallet.id);
|
|
24859
|
+
const net = (parseFloat(summary.received) - parseFloat(summary.sent)).toFixed(2);
|
|
24860
|
+
return { content: `Wallet ${wallet.address}: ${net} USDC (received: ${summary.received}, sent: ${summary.sent})` };
|
|
24861
|
+
}
|
|
24862
|
+
case "quoroom_wallet_send": {
|
|
24863
|
+
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 };
|
|
24864
|
+
}
|
|
24865
|
+
case "quoroom_wallet_history": {
|
|
24866
|
+
const wallet = getWalletByRoom(db2, roomId);
|
|
24867
|
+
if (!wallet) return { content: "No wallet found for this room.", isError: true };
|
|
24868
|
+
const limit = Math.min(Number(args.limit) || 10, 50);
|
|
24869
|
+
const txs = listWalletTransactions(db2, wallet.id, limit);
|
|
24870
|
+
if (txs.length === 0) return { content: "No transactions yet." };
|
|
24871
|
+
const lines = txs.map((tx) => `[${tx.type}] ${tx.amount} USDC \u2014 ${tx.description ?? ""} (${tx.status})`).join("\n");
|
|
24872
|
+
return { content: lines };
|
|
24873
|
+
}
|
|
24612
24874
|
default:
|
|
24613
24875
|
return { content: `Unknown tool: ${toolName}`, isError: true };
|
|
24614
24876
|
}
|
|
@@ -24873,7 +25135,8 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
|
|
|
24873
25135
|
id: g.id,
|
|
24874
25136
|
goal: g.description,
|
|
24875
25137
|
progress: g.progress,
|
|
24876
|
-
status: g.status
|
|
25138
|
+
status: g.status,
|
|
25139
|
+
assignedWorkerId: g.assignedWorkerId
|
|
24877
25140
|
}));
|
|
24878
25141
|
const roomWorkers = listRoomWorkers(db2, roomId);
|
|
24879
25142
|
const roomTasks = listTasks(db2, roomId, "active").slice(0, 10);
|
|
@@ -24979,10 +25242,21 @@ ${skillContent}` : ""
|
|
|
24979
25242
|
${status2.room.goal}`);
|
|
24980
25243
|
}
|
|
24981
25244
|
if (goalUpdates.length > 0) {
|
|
25245
|
+
const workerMap = new Map(roomWorkers.map((w) => [w.id, w.name]));
|
|
24982
25246
|
contextParts.push(`## Active Goals
|
|
24983
|
-
${goalUpdates.map(
|
|
24984
|
-
|
|
24985
|
-
|
|
25247
|
+
${goalUpdates.map((g) => {
|
|
25248
|
+
const assignee = g.assignedWorkerId ? ` \u2192 ${workerMap.get(g.assignedWorkerId) ?? `Worker #${g.assignedWorkerId}`}` : "";
|
|
25249
|
+
return `- [#${g.id}] [${Math.round(g.progress * 100)}%] ${g.goal} (${g.status})${assignee}`;
|
|
25250
|
+
}).join("\n")}`);
|
|
25251
|
+
const myTasks = status2.activeGoals.filter((g) => g.assignedWorkerId === worker.id);
|
|
25252
|
+
if (myTasks.length > 0) {
|
|
25253
|
+
contextParts.push(`## Your Assigned Tasks
|
|
25254
|
+
${myTasks.map(
|
|
25255
|
+
(g) => `- [#${g.id}] [${Math.round(g.progress * 100)}%] ${g.description}`
|
|
25256
|
+
).join("\n")}
|
|
25257
|
+
|
|
25258
|
+
These tasks were delegated to you. Prioritize completing them and report progress.`);
|
|
25259
|
+
}
|
|
24986
25260
|
}
|
|
24987
25261
|
const memoryEntities = listEntities(db2, roomId).slice(0, 20);
|
|
24988
25262
|
if (memoryEntities.length > 0) {
|
|
@@ -25011,19 +25285,21 @@ ${recentResolved.map((d) => {
|
|
|
25011
25285
|
return `- ${icon} ${d.status}: "${d.proposal.slice(0, 120)}"`;
|
|
25012
25286
|
}).join("\n")}`);
|
|
25013
25287
|
}
|
|
25014
|
-
const
|
|
25015
|
-
const
|
|
25016
|
-
if (
|
|
25017
|
-
contextParts.push(`## Pending
|
|
25018
|
-
${
|
|
25288
|
+
const myKeeperMessages = pendingEscalations.filter((e) => e.fromAgentId === worker.id && !e.toAgentId);
|
|
25289
|
+
const incomingWorkerMessages = pendingEscalations.filter((e) => e.toAgentId === worker.id && e.fromAgentId !== worker.id);
|
|
25290
|
+
if (myKeeperMessages.length > 0) {
|
|
25291
|
+
contextParts.push(`## Pending Messages to Keeper (awaiting reply)
|
|
25292
|
+
${myKeeperMessages.map(
|
|
25019
25293
|
(e) => `- #${e.id}: ${e.question}`
|
|
25020
25294
|
).join("\n")}`);
|
|
25021
25295
|
}
|
|
25022
|
-
if (
|
|
25023
|
-
|
|
25024
|
-
|
|
25025
|
-
|
|
25026
|
-
|
|
25296
|
+
if (incomingWorkerMessages.length > 0) {
|
|
25297
|
+
const senderNames = new Map(roomWorkers.map((w) => [w.id, w.name]));
|
|
25298
|
+
contextParts.push(`## Messages from Other Workers
|
|
25299
|
+
${incomingWorkerMessages.map((e) => {
|
|
25300
|
+
const sender = senderNames.get(e.fromAgentId ?? 0) ?? `Worker #${e.fromAgentId}`;
|
|
25301
|
+
return `- #${e.id} from ${sender}: ${e.question}`;
|
|
25302
|
+
}).join("\n")}`);
|
|
25027
25303
|
}
|
|
25028
25304
|
if (recentKeeperAnswers.length > 0) {
|
|
25029
25305
|
contextParts.push(`## Keeper Answers (recent)
|
|
@@ -25051,6 +25327,14 @@ ${roomTasks.map(
|
|
|
25051
25327
|
(t) => `- #${t.id} "${t.name}" [${t.triggerType}] \u2014 ${t.status}`
|
|
25052
25328
|
).join("\n")}`);
|
|
25053
25329
|
}
|
|
25330
|
+
const wallet = getWalletByRoom(db2, roomId);
|
|
25331
|
+
if (wallet) {
|
|
25332
|
+
const summary = getWalletTransactionSummary(db2, wallet.id);
|
|
25333
|
+
const net = (parseFloat(summary.received) - parseFloat(summary.sent)).toFixed(2);
|
|
25334
|
+
contextParts.push(`## Wallet
|
|
25335
|
+
Address: ${wallet.address}
|
|
25336
|
+
Balance: ${net} USDC (received: ${summary.received}, spent: ${summary.sent})`);
|
|
25337
|
+
}
|
|
25054
25338
|
if (unreadMessages.length > 0) {
|
|
25055
25339
|
contextParts.push(`## Unread Messages
|
|
25056
25340
|
${unreadMessages.map(
|
|
@@ -25088,26 +25372,62 @@ ${top3.map(
|
|
|
25088
25372
|
}
|
|
25089
25373
|
contextParts.push(`## Execution Settings
|
|
25090
25374
|
${settingsParts.join("\n")}`);
|
|
25375
|
+
const STUCK_THRESHOLD_CYCLES = 2;
|
|
25376
|
+
const productiveCallCount = countProductiveToolCalls(db2, worker.id, STUCK_THRESHOLD_CYCLES);
|
|
25377
|
+
const recentCompletedCycles = listRoomCycles(db2, roomId, 5).filter((c) => c.workerId === worker.id && c.status === "completed");
|
|
25378
|
+
const isStuck = recentCompletedCycles.length >= STUCK_THRESHOLD_CYCLES && productiveCallCount === 0;
|
|
25379
|
+
if (isStuck) {
|
|
25380
|
+
contextParts.push(`## \u26A0 STUCK DETECTED
|
|
25381
|
+
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:
|
|
25382
|
+
- Try a different web search query
|
|
25383
|
+
- Store what you know in memory even if incomplete
|
|
25384
|
+
- Update goal progress with what you've learned
|
|
25385
|
+
- Message the keeper if you're blocked
|
|
25386
|
+
Do NOT repeat the same approach. Pivot immediately.`);
|
|
25387
|
+
logBuffer.addSynthetic("system", `Stuck detector: 0 productive tool calls in last ${STUCK_THRESHOLD_CYCLES} cycles \u2014 injecting pivot directive`);
|
|
25388
|
+
}
|
|
25091
25389
|
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
25390
|
const isClaude = model === "claude" || model.startsWith("claude-");
|
|
25093
25391
|
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
|
-
|
|
25104
|
-
|
|
25392
|
+
const allowListRaw = status2.room.allowedTools?.trim() || null;
|
|
25393
|
+
const allowSet = allowListRaw ? new Set(allowListRaw.split(",").map((s) => s.trim())) : null;
|
|
25394
|
+
const has = (name) => !allowSet || allowSet.has(name);
|
|
25395
|
+
const toolLines = [];
|
|
25396
|
+
const goalTools = ["quoroom_set_goal", "quoroom_update_progress", "quoroom_create_subgoal", "quoroom_delegate_task", "quoroom_complete_goal", "quoroom_abandon_goal"].filter(has);
|
|
25397
|
+
if (goalTools.length) toolLines.push(`**Goals:** ${goalTools.join(", ")}`);
|
|
25398
|
+
const govTools = ["quoroom_propose", "quoroom_vote"].filter(has);
|
|
25399
|
+
if (govTools.length) toolLines.push(`**Governance:** ${govTools.join(", ")}`);
|
|
25400
|
+
const workerTools = ["quoroom_create_worker", "quoroom_update_worker"].filter(has);
|
|
25401
|
+
if (workerTools.length) toolLines.push(`**Workers:** ${workerTools.join(", ")}`);
|
|
25402
|
+
if (has("quoroom_schedule")) toolLines.push("**Tasks:** quoroom_schedule");
|
|
25403
|
+
const memTools = ["quoroom_remember", "quoroom_recall"].filter(has);
|
|
25404
|
+
if (memTools.length) toolLines.push(`**Memory:** ${memTools.join(", ")}`);
|
|
25405
|
+
const walletToolNames = isCli ? ["quoroom_wallet_balance", "quoroom_wallet_send", "quoroom_wallet_history", "quoroom_wallet_topup"] : ["quoroom_wallet_balance", "quoroom_wallet_send", "quoroom_wallet_history"];
|
|
25406
|
+
const filteredWallet = walletToolNames.filter(has);
|
|
25407
|
+
if (filteredWallet.length) toolLines.push(`**Wallet:** ${filteredWallet.join(", ")}`);
|
|
25408
|
+
const webToolNames = isCli ? null : ["quoroom_web_search", "quoroom_web_fetch", "quoroom_browser"];
|
|
25409
|
+
if (isCli) {
|
|
25410
|
+
if (has("quoroom_web_search") || has("quoroom_web_fetch")) toolLines.push("**Web:** (use your built-in web search and fetch tools)");
|
|
25411
|
+
} else {
|
|
25412
|
+
const filteredWeb = (webToolNames || []).filter(has);
|
|
25413
|
+
if (filteredWeb.length) toolLines.push(`**Web:** ${filteredWeb.join(", ")}`);
|
|
25414
|
+
}
|
|
25415
|
+
const commsToolNames = isCli ? ["quoroom_send_message", "quoroom_inbox_list", "quoroom_inbox_send_room", "quoroom_inbox_reply"] : ["quoroom_send_message"];
|
|
25416
|
+
const filteredComms = commsToolNames.filter(has);
|
|
25417
|
+
if (filteredComms.length) {
|
|
25418
|
+
if (isCli) toolLines.push(`**Comms:** ${filteredComms.map((t) => t === "quoroom_send_message" ? `${t} (message keeper or worker)` : t === "quoroom_inbox_list" ? `${t} (inter-room)` : t).join(", ")}`);
|
|
25419
|
+
else toolLines.push(`**Comms:** ${filteredComms.join(", ")}`);
|
|
25420
|
+
}
|
|
25421
|
+
if (has("quoroom_configure_room")) toolLines.push(`**Settings:** quoroom_configure_room${selfRegulateHint}`);
|
|
25422
|
+
const toolList = toolLines.join("\n");
|
|
25105
25423
|
contextParts.push(`## Instructions
|
|
25106
25424
|
Based on the current state, decide what to do next and call the appropriate tools. Available tools:
|
|
25107
25425
|
|
|
25108
25426
|
${toolList}
|
|
25109
25427
|
|
|
25110
|
-
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
|
|
25428
|
+
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 quoroom_send_message (to="keeper"), but never block on a response. If the keeper hasn't replied, proceed with your best judgment.
|
|
25429
|
+
|
|
25430
|
+
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.
|
|
25111
25431
|
|
|
25112
25432
|
${toolCallInstruction}`);
|
|
25113
25433
|
const prompt = contextParts.join("\n\n");
|
|
@@ -25117,8 +25437,9 @@ ${toolCallInstruction}`);
|
|
|
25117
25437
|
logBuffer.flush();
|
|
25118
25438
|
const apiKey = apiKeyEarly;
|
|
25119
25439
|
const needsQueenTools = model === "openai" || model.startsWith("openai:") || model === "anthropic" || model.startsWith("anthropic:") || model.startsWith("claude-api:");
|
|
25440
|
+
const filteredToolDefs = allowSet ? QUEEN_TOOL_DEFINITIONS.filter((t) => allowSet.has(t.function.name)) : QUEEN_TOOL_DEFINITIONS;
|
|
25120
25441
|
const apiToolOpts = needsQueenTools ? {
|
|
25121
|
-
toolDefs:
|
|
25442
|
+
toolDefs: filteredToolDefs,
|
|
25122
25443
|
onToolCall: async (toolName, args) => {
|
|
25123
25444
|
logBuffer.addSynthetic("tool_call", `\u2192 ${toolName}(${JSON.stringify(args)})`);
|
|
25124
25445
|
const result2 = await executeQueenTool(db2, roomId, worker.id, toolName, args);
|
|
@@ -25134,6 +25455,8 @@ ${toolCallInstruction}`);
|
|
|
25134
25455
|
timeoutMs: 5 * 60 * 1e3,
|
|
25135
25456
|
maxTurns: maxTurns ?? 10,
|
|
25136
25457
|
onConsoleLog: logBuffer.onConsoleLog,
|
|
25458
|
+
// CLI models: block non-quoroom MCP tools (daymon, etc.)
|
|
25459
|
+
disallowedTools: isCli ? "mcp__daymon*" : void 0,
|
|
25137
25460
|
// CLI models: pass resumeSessionId for native --resume
|
|
25138
25461
|
resumeSessionId,
|
|
25139
25462
|
// API models: pass conversation history + persistence callback
|
|
@@ -25148,6 +25471,23 @@ ${toolCallInstruction}`);
|
|
|
25148
25471
|
if (rateLimitInfo) {
|
|
25149
25472
|
throw new RateLimitError(rateLimitInfo);
|
|
25150
25473
|
}
|
|
25474
|
+
if (result.exitCode !== 0) {
|
|
25475
|
+
const errorDetail = result.output?.trim() || `exit code ${result.exitCode}`;
|
|
25476
|
+
logBuffer.addSynthetic("error", `Agent execution failed: ${errorDetail.slice(0, 500)}`);
|
|
25477
|
+
logBuffer.flush();
|
|
25478
|
+
completeWorkerCycle(db2, cycle.id, errorDetail.slice(0, 500), result.usage);
|
|
25479
|
+
options?.onCycleLifecycle?.("failed", cycle.id, roomId);
|
|
25480
|
+
logRoomActivity(
|
|
25481
|
+
db2,
|
|
25482
|
+
roomId,
|
|
25483
|
+
"error",
|
|
25484
|
+
`Agent cycle failed (${worker.name}): ${errorDetail.slice(0, 200)}`,
|
|
25485
|
+
errorDetail,
|
|
25486
|
+
worker.id
|
|
25487
|
+
);
|
|
25488
|
+
updateAgentState(db2, worker.id, "idle");
|
|
25489
|
+
return result.output;
|
|
25490
|
+
}
|
|
25151
25491
|
if (isCli && result.sessionId) {
|
|
25152
25492
|
saveAgentSession(db2, worker.id, { sessionId: result.sessionId, model });
|
|
25153
25493
|
}
|
|
@@ -25556,6 +25896,7 @@ function registerRoomRoutes(router) {
|
|
|
25556
25896
|
const trimmed = body.queenNickname.trim().replace(/\s+/g, "");
|
|
25557
25897
|
if (trimmed.length > 0 && trimmed.length <= 40) updates.queenNickname = trimmed;
|
|
25558
25898
|
}
|
|
25899
|
+
if (body.allowedTools !== void 0) updates.allowedTools = body.allowedTools || null;
|
|
25559
25900
|
if (body.config !== void 0 && typeof body.config === "object" && body.config !== null) {
|
|
25560
25901
|
updates.config = { ...room.config, ...body.config };
|
|
25561
25902
|
}
|
|
@@ -25621,7 +25962,7 @@ function registerRoomRoutes(router) {
|
|
|
25621
25962
|
if (!room) return { status: 404, error: "Room not found" };
|
|
25622
25963
|
if (!room.queenWorkerId) return { status: 404, error: "No queen worker" };
|
|
25623
25964
|
const worker = getWorker(ctx.db, room.queenWorkerId);
|
|
25624
|
-
const model = worker?.model ?? null;
|
|
25965
|
+
const model = worker?.model ?? room.workerModel ?? null;
|
|
25625
25966
|
const auth = await getModelAuthStatus(ctx.db, roomId, model);
|
|
25626
25967
|
return {
|
|
25627
25968
|
data: {
|
|
@@ -25745,6 +26086,29 @@ function registerWorkerRoutes(router) {
|
|
|
25745
26086
|
eventBus.emit("workers", "worker:deleted", { id });
|
|
25746
26087
|
return { data: { ok: true } };
|
|
25747
26088
|
});
|
|
26089
|
+
router.post("/api/workers/:id/start", (ctx) => {
|
|
26090
|
+
const id = Number(ctx.params.id);
|
|
26091
|
+
const worker = getWorker(ctx.db, id);
|
|
26092
|
+
if (!worker) return { status: 404, error: "Worker not found" };
|
|
26093
|
+
if (!worker.roomId) return { status: 400, error: "Worker has no room" };
|
|
26094
|
+
const room = getRoom(ctx.db, worker.roomId);
|
|
26095
|
+
if (!room) return { status: 404, error: "Room not found" };
|
|
26096
|
+
if (room.status !== "active") return { status: 400, error: "Room is not active" };
|
|
26097
|
+
triggerAgent(ctx.db, worker.roomId, id, {
|
|
26098
|
+
onCycleLogEntry: (entry) => eventBus.emit(`cycle:${entry.cycleId}`, "cycle:log", entry),
|
|
26099
|
+
onCycleLifecycle: (event, cycleId) => eventBus.emit(`room:${worker.roomId}`, `cycle:${event}`, { cycleId, roomId: worker.roomId })
|
|
26100
|
+
});
|
|
26101
|
+
eventBus.emit("workers", "worker:started", { id, roomId: worker.roomId });
|
|
26102
|
+
return { data: { ok: true, running: true } };
|
|
26103
|
+
});
|
|
26104
|
+
router.post("/api/workers/:id/stop", (ctx) => {
|
|
26105
|
+
const id = Number(ctx.params.id);
|
|
26106
|
+
const worker = getWorker(ctx.db, id);
|
|
26107
|
+
if (!worker) return { status: 404, error: "Worker not found" };
|
|
26108
|
+
pauseAgent(ctx.db, id);
|
|
26109
|
+
eventBus.emit("workers", "worker:stopped", { id });
|
|
26110
|
+
return { data: { ok: true, running: false } };
|
|
26111
|
+
});
|
|
25748
26112
|
router.get("/api/rooms/:roomId/workers", (ctx) => {
|
|
25749
26113
|
const workers = listRoomWorkers(ctx.db, Number(ctx.params.roomId));
|
|
25750
26114
|
return { data: workers };
|
|
@@ -28047,7 +28411,15 @@ function registerChatRoutes(router) {
|
|
|
28047
28411
|
// 3 minutes
|
|
28048
28412
|
});
|
|
28049
28413
|
if (result.exitCode !== 0 || result.timedOut) {
|
|
28050
|
-
const
|
|
28414
|
+
const rawOutput = result.output?.trim();
|
|
28415
|
+
let reason;
|
|
28416
|
+
if (result.timedOut) {
|
|
28417
|
+
reason = "Chat request timed out";
|
|
28418
|
+
} else if (rawOutput) {
|
|
28419
|
+
reason = rawOutput;
|
|
28420
|
+
} else {
|
|
28421
|
+
reason = `Chat execution failed (model: ${model}, exit code: ${result.exitCode})`;
|
|
28422
|
+
}
|
|
28051
28423
|
return { status: result.timedOut ? 504 : 502, error: reason.slice(0, 500) };
|
|
28052
28424
|
}
|
|
28053
28425
|
const response = result.output || "No response";
|
|
@@ -28134,6 +28506,7 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|
|
28134
28506
|
queen_nickname TEXT,
|
|
28135
28507
|
chat_session_id TEXT,
|
|
28136
28508
|
referred_by_code TEXT,
|
|
28509
|
+
allowed_tools TEXT,
|
|
28137
28510
|
created_at DATETIME DEFAULT (datetime('now','localtime')),
|
|
28138
28511
|
updated_at DATETIME DEFAULT (datetime('now','localtime'))
|
|
28139
28512
|
);
|
|
@@ -28618,6 +28991,13 @@ function runMigrations(database, log = console.log) {
|
|
|
28618
28991
|
database.exec(`ALTER TABLE workers ADD COLUMN max_turns INTEGER`);
|
|
28619
28992
|
log("Migrated: added cycle_gap_ms and max_turns columns to workers");
|
|
28620
28993
|
}
|
|
28994
|
+
const hasRoomAllowedTools = database.prepare(
|
|
28995
|
+
`SELECT name FROM pragma_table_info('rooms') WHERE name='allowed_tools'`
|
|
28996
|
+
).get()?.name;
|
|
28997
|
+
if (!hasRoomAllowedTools) {
|
|
28998
|
+
database.exec(`ALTER TABLE rooms ADD COLUMN allowed_tools TEXT`);
|
|
28999
|
+
log("Migrated: added allowed_tools column to rooms");
|
|
29000
|
+
}
|
|
28621
29001
|
const ollamaWorkers = database.prepare(`SELECT id FROM workers WHERE model LIKE 'ollama:%'`).all();
|
|
28622
29002
|
if (ollamaWorkers.length > 0) {
|
|
28623
29003
|
database.prepare(`UPDATE workers SET model = 'claude' WHERE model LIKE 'ollama:%'`).run();
|
|
@@ -28835,7 +29215,7 @@ function semverGt(a, b) {
|
|
|
28835
29215
|
}
|
|
28836
29216
|
function getCurrentVersion() {
|
|
28837
29217
|
try {
|
|
28838
|
-
return true ? "0.1.
|
|
29218
|
+
return true ? "0.1.23" : null.version;
|
|
28839
29219
|
} catch {
|
|
28840
29220
|
return "0.0.0";
|
|
28841
29221
|
}
|
|
@@ -28992,7 +29372,7 @@ var cachedVersion = null;
|
|
|
28992
29372
|
function getVersion3() {
|
|
28993
29373
|
if (cachedVersion) return cachedVersion;
|
|
28994
29374
|
try {
|
|
28995
|
-
cachedVersion = true ? "0.1.
|
|
29375
|
+
cachedVersion = true ? "0.1.23" : null.version;
|
|
28996
29376
|
} catch {
|
|
28997
29377
|
cachedVersion = "unknown";
|
|
28998
29378
|
}
|
|
@@ -29659,6 +30039,12 @@ function registerRoomMessageRoutes(router) {
|
|
|
29659
30039
|
if (!msg) return { status: 404, error: "Message not found" };
|
|
29660
30040
|
return { data: msg };
|
|
29661
30041
|
});
|
|
30042
|
+
router.post("/api/rooms/:roomId/messages/read-all", (ctx) => {
|
|
30043
|
+
const roomId = Number(ctx.params.roomId);
|
|
30044
|
+
const count = markAllRoomMessagesRead(ctx.db, roomId);
|
|
30045
|
+
if (count > 0) eventBus.emit(`room:${roomId}`, "room_message:updated", { roomId, allRead: true });
|
|
30046
|
+
return { data: { ok: true, count } };
|
|
30047
|
+
});
|
|
29662
30048
|
router.post("/api/rooms/:roomId/messages/:id/read", (ctx) => {
|
|
29663
30049
|
const roomId = Number(ctx.params.roomId);
|
|
29664
30050
|
const id = Number(ctx.params.id);
|