quoroom 0.1.39 → 0.1.41

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/server.js CHANGED
@@ -6852,7 +6852,7 @@ CREATE TABLE IF NOT EXISTS rooms (
6852
6852
  max_concurrent_tasks INTEGER NOT NULL DEFAULT 3,
6853
6853
  worker_model TEXT NOT NULL DEFAULT 'claude',
6854
6854
  queen_cycle_gap_ms INTEGER NOT NULL DEFAULT 1800000,
6855
- queen_max_turns INTEGER NOT NULL DEFAULT 3,
6855
+ queen_max_turns INTEGER NOT NULL DEFAULT 50,
6856
6856
  queen_quiet_from TEXT,
6857
6857
  queen_quiet_until TEXT,
6858
6858
  config TEXT,
@@ -7206,23 +7206,6 @@ CREATE TABLE IF NOT EXISTS room_messages (
7206
7206
  CREATE INDEX IF NOT EXISTS idx_room_messages_room ON room_messages(room_id);
7207
7207
  CREATE INDEX IF NOT EXISTS idx_room_messages_status ON room_messages(status);
7208
7208
 
7209
- -- Stations
7210
- CREATE TABLE IF NOT EXISTS stations (
7211
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7212
- room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
7213
- name TEXT NOT NULL,
7214
- provider TEXT NOT NULL,
7215
- external_id TEXT,
7216
- tier TEXT NOT NULL,
7217
- region TEXT,
7218
- status TEXT NOT NULL DEFAULT 'provisioning',
7219
- monthly_cost REAL NOT NULL DEFAULT 0,
7220
- config TEXT,
7221
- created_at DATETIME DEFAULT (datetime('now','localtime')),
7222
- updated_at DATETIME DEFAULT (datetime('now','localtime'))
7223
- );
7224
- CREATE INDEX IF NOT EXISTS idx_stations_room ON stations(room_id);
7225
-
7226
7209
  -- Worker cycles (agent loop execution tracking)
7227
7210
  CREATE TABLE IF NOT EXISTS worker_cycles (
7228
7211
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -7401,24 +7384,24 @@ var init_constants = __esm({
7401
7384
  "base-sepolia": "0x8004A818BFB912233c491871b3d84c89A494BD9e"
7402
7385
  };
7403
7386
  QUEEN_DEFAULTS_BY_PLAN = {
7404
- none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 30 },
7405
- // 10 min gap, 30 turns
7406
- pro: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 30 },
7407
- // 5 min gap, 30 turns
7408
- max: { queenCycleGapMs: 30 * 1e3, queenMaxTurns: 30 },
7409
- // 30s gap, 30 turns
7410
- api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 }
7411
- // 2 min gap, 30 turns
7387
+ none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 50 },
7388
+ // 10 min gap, 50 turns
7389
+ pro: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 50 },
7390
+ // 5 min gap, 50 turns
7391
+ max: { queenCycleGapMs: 30 * 1e3, queenMaxTurns: 50 },
7392
+ // 30s gap, 50 turns
7393
+ api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 50 }
7394
+ // 2 min gap, 50 turns
7412
7395
  };
7413
7396
  CHATGPT_DEFAULTS_BY_PLAN = {
7414
- none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 30 },
7415
- // 10 min gap, 30 turns
7416
- plus: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 30 },
7417
- // 5 min gap, 30 turns
7418
- pro: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 },
7419
- // 2 min gap, 30 turns
7420
- api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 }
7421
- // 2 min gap, 30 turns
7397
+ none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 50 },
7398
+ // 10 min gap, 50 turns
7399
+ plus: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 50 },
7400
+ // 5 min gap, 50 turns
7401
+ pro: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 50 },
7402
+ // 2 min gap, 50 turns
7403
+ api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 50 }
7404
+ // 2 min gap, 50 turns
7422
7405
  };
7423
7406
  WORKER_ROLE_PRESETS = {
7424
7407
  guardian: {
@@ -7579,7 +7562,6 @@ __export(db_queries_exports, {
7579
7562
  createRoom: () => createRoom,
7580
7563
  createRoomMessage: () => createRoomMessage,
7581
7564
  createSkill: () => createSkill,
7582
- createStation: () => createStation,
7583
7565
  createTask: () => createTask,
7584
7566
  createTaskRun: () => createTaskRun,
7585
7567
  createWallet: () => createWallet,
@@ -7596,7 +7578,6 @@ __export(db_queries_exports, {
7596
7578
  deleteRoom: () => deleteRoom,
7597
7579
  deleteRoomMessage: () => deleteRoomMessage,
7598
7580
  deleteSkill: () => deleteSkill,
7599
- deleteStation: () => deleteStation,
7600
7581
  deleteTask: () => deleteTask,
7601
7582
  deleteWallet: () => deleteWallet,
7602
7583
  deleteWatch: () => deleteWatch,
@@ -7650,7 +7631,6 @@ __export(db_queries_exports, {
7650
7631
  getSessionRunCount: () => getSessionRunCount,
7651
7632
  getSetting: () => getSetting,
7652
7633
  getSkill: () => getSkill,
7653
- getStation: () => getStation,
7654
7634
  getSubGoals: () => getSubGoals,
7655
7635
  getTask: () => getTask,
7656
7636
  getTaskByWebhookToken: () => getTaskByWebhookToken,
@@ -7694,7 +7674,6 @@ __export(db_queries_exports, {
7694
7674
  listRooms: () => listRooms,
7695
7675
  listRunsByRoom: () => listRunsByRoom,
7696
7676
  listSkills: () => listSkills,
7697
- listStations: () => listStations,
7698
7677
  listTasks: () => listTasks,
7699
7678
  listWalletTransactions: () => listWalletTransactions,
7700
7679
  listWallets: () => listWallets,
@@ -7735,7 +7714,6 @@ __export(db_queries_exports, {
7735
7714
  updateRoom: () => updateRoom,
7736
7715
  updateRoomMessageStatus: () => updateRoomMessageStatus,
7737
7716
  updateSkill: () => updateSkill,
7738
- updateStation: () => updateStation,
7739
7717
  updateTask: () => updateTask,
7740
7718
  updateTaskRunProgress: () => updateTaskRunProgress,
7741
7719
  updateTaskRunSessionId: () => updateTaskRunSessionId,
@@ -8528,7 +8506,7 @@ function mapRoomRow(row) {
8528
8506
  maxConcurrentTasks: row.max_concurrent_tasks ?? 3,
8529
8507
  workerModel: row.worker_model ?? "claude",
8530
8508
  queenCycleGapMs: row.queen_cycle_gap_ms ?? 18e5,
8531
- queenMaxTurns: row.queen_max_turns ?? 3,
8509
+ queenMaxTurns: row.queen_max_turns ?? 50,
8532
8510
  queenQuietFrom: row.queen_quiet_from ?? null,
8533
8511
  queenQuietUntil: row.queen_quiet_until ?? null,
8534
8512
  config: config2,
@@ -9170,91 +9148,6 @@ function getWalletTransactionSummary(db2, walletId) {
9170
9148
  ).get(walletId);
9171
9149
  return { received: received.total.toString(), sent: sent.total.toString() };
9172
9150
  }
9173
- function mapStationRow(row) {
9174
- let config2 = null;
9175
- if (row.config && typeof row.config === "string") {
9176
- try {
9177
- config2 = JSON.parse(row.config);
9178
- } catch {
9179
- }
9180
- }
9181
- return {
9182
- id: row.id,
9183
- roomId: row.room_id,
9184
- name: row.name,
9185
- provider: row.provider,
9186
- externalId: row.external_id,
9187
- tier: row.tier,
9188
- region: row.region,
9189
- status: row.status,
9190
- monthlyCost: row.monthly_cost,
9191
- config: config2,
9192
- createdAt: row.created_at,
9193
- updatedAt: row.updated_at
9194
- };
9195
- }
9196
- function createStation(db2, roomId, name, provider, tier, opts) {
9197
- const result = db2.prepare("INSERT INTO stations (room_id, name, provider, tier, external_id, region, monthly_cost, config, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(
9198
- roomId,
9199
- name,
9200
- provider,
9201
- tier,
9202
- opts?.externalId ?? null,
9203
- opts?.region ?? null,
9204
- opts?.monthlyCost ?? 0,
9205
- opts?.config ? JSON.stringify(opts.config) : null,
9206
- opts?.status ?? "provisioning"
9207
- );
9208
- return getStation(db2, result.lastInsertRowid);
9209
- }
9210
- function getStation(db2, id) {
9211
- const row = db2.prepare("SELECT * FROM stations WHERE id = ?").get(id);
9212
- return row ? mapStationRow(row) : null;
9213
- }
9214
- function listStations(db2, roomId, status) {
9215
- if (roomId && status) {
9216
- const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
9217
- return rows2.map(mapStationRow);
9218
- }
9219
- if (roomId) {
9220
- const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
9221
- return rows2.map(mapStationRow);
9222
- }
9223
- if (status) {
9224
- const rows2 = db2.prepare("SELECT * FROM stations WHERE status = ? ORDER BY created_at DESC").all(status);
9225
- return rows2.map(mapStationRow);
9226
- }
9227
- const rows = db2.prepare("SELECT * FROM stations ORDER BY created_at DESC").all();
9228
- return rows.map(mapStationRow);
9229
- }
9230
- function updateStation(db2, id, updates) {
9231
- const parts = [];
9232
- const values = [];
9233
- if (updates.externalId !== void 0) {
9234
- parts.push("external_id = ?");
9235
- values.push(updates.externalId);
9236
- }
9237
- if (updates.status !== void 0) {
9238
- parts.push("status = ?");
9239
- values.push(updates.status);
9240
- }
9241
- if (updates.monthlyCost !== void 0) {
9242
- parts.push("monthly_cost = ?");
9243
- values.push(updates.monthlyCost);
9244
- }
9245
- if (updates.config !== void 0) {
9246
- parts.push("config = ?");
9247
- values.push(JSON.stringify(updates.config));
9248
- }
9249
- if (parts.length === 0) return getStation(db2, id);
9250
- parts.push("updated_at = datetime('now','localtime')");
9251
- values.push(id);
9252
- db2.prepare(`UPDATE stations SET ${parts.join(", ")} WHERE id = ?`).run(...values);
9253
- return getStation(db2, id);
9254
- }
9255
- function deleteStation(db2, id) {
9256
- db2.prepare("DELETE FROM stations WHERE id = ?").run(id);
9257
- }
9258
9151
  function mapChatMessageRow(row) {
9259
9152
  return {
9260
9153
  id: row.id,
@@ -9461,16 +9354,13 @@ function ensureClerkWorker(db2) {
9461
9354
  }
9462
9355
  function getRevenueSummary(db2, roomId) {
9463
9356
  const wallet = getWalletByRoom(db2, roomId);
9464
- if (!wallet) return { totalIncome: 0, totalExpenses: 0, netProfit: 0, stationCosts: 0, transactionCount: 0 };
9357
+ if (!wallet) return { totalIncome: 0, totalExpenses: 0, netProfit: 0, transactionCount: 0 };
9465
9358
  const income = db2.prepare(
9466
9359
  "SELECT COALESCE(SUM(CAST(amount AS REAL)), 0) as total FROM wallet_transactions WHERE wallet_id = ? AND type IN ('receive', 'fund')"
9467
9360
  ).get(wallet.id);
9468
9361
  const expenses = db2.prepare(
9469
9362
  "SELECT COALESCE(SUM(CAST(amount AS REAL)), 0) as total FROM wallet_transactions WHERE wallet_id = ? AND type IN ('send', 'purchase')"
9470
9363
  ).get(wallet.id);
9471
- const stationCosts = db2.prepare(
9472
- "SELECT COALESCE(SUM(CAST(amount AS REAL)), 0) as total FROM wallet_transactions WHERE wallet_id = ? AND category = 'station_cost'"
9473
- ).get(wallet.id);
9474
9364
  const count = db2.prepare(
9475
9365
  "SELECT COUNT(*) as cnt FROM wallet_transactions WHERE wallet_id = ?"
9476
9366
  ).get(wallet.id);
@@ -9478,7 +9368,6 @@ function getRevenueSummary(db2, roomId) {
9478
9368
  totalIncome: income.total,
9479
9369
  totalExpenses: expenses.total,
9480
9370
  netProfit: income.total - expenses.total,
9481
- stationCosts: stationCosts.total,
9482
9371
  transactionCount: count.cnt
9483
9372
  };
9484
9373
  }
@@ -9781,6 +9670,10 @@ function upsertSetting(database, key, value) {
9781
9670
  }
9782
9671
  function runMigrations(database, log = console.log) {
9783
9672
  database.exec(SCHEMA);
9673
+ const legacyQueenTurnsUpdated = database.prepare(`UPDATE rooms SET queen_max_turns = 50 WHERE queen_max_turns = 3`).run().changes;
9674
+ if (legacyQueenTurnsUpdated > 0) {
9675
+ log(`Migrated: updated ${legacyQueenTurnsUpdated} room(s) queen_max_turns from 3 to 50`);
9676
+ }
9784
9677
  if (!database.prepare("SELECT value FROM settings WHERE key = ?").get("keeper_referral_code")) {
9785
9678
  const code = (0, import_crypto.randomBytes)(6).toString("base64url").slice(0, 10);
9786
9679
  upsertSetting(database, "keeper_referral_code", code);
@@ -9879,6 +9772,7 @@ function runMigrations(database, log = console.log) {
9879
9772
  log(`Migrated: reset ${ollamaRooms.length} room worker_model(s) to 'claude'`);
9880
9773
  }
9881
9774
  database.prepare(`UPDATE rooms SET autonomy_mode = 'semi' WHERE autonomy_mode IS NULL OR autonomy_mode != 'semi'`).run();
9775
+ database.exec(`DROP TABLE IF EXISTS stations`);
9882
9776
  log("Database schema initialized");
9883
9777
  }
9884
9778
  var import_crypto;
@@ -9913,7 +9807,8 @@ async function initEngine() {
9913
9807
  if (pipeline || pipelineLoading) return;
9914
9808
  pipelineLoading = true;
9915
9809
  try {
9916
- const { pipeline: createPipeline } = await import("@huggingface/transformers");
9810
+ const { pipeline: createPipeline, env } = await import("@huggingface/transformers");
9811
+ env.cacheDir = (0, import_path.join)((0, import_os.homedir)(), ".quoroom", "cache");
9917
9812
  const pipe2 = await createPipeline("feature-extraction", MODEL_NAME, {
9918
9813
  dtype: "fp32",
9919
9814
  revision: "main"
@@ -9944,10 +9839,12 @@ async function embed(text) {
9944
9839
  function vectorToBlob(vec) {
9945
9840
  return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
9946
9841
  }
9947
- var pipeline, pipelineLoading, sqliteVecLoaded, MODEL_NAME;
9842
+ var import_os, import_path, pipeline, pipelineLoading, sqliteVecLoaded, MODEL_NAME;
9948
9843
  var init_embeddings = __esm({
9949
9844
  "src/shared/embeddings.ts"() {
9950
9845
  "use strict";
9846
+ import_os = require("os");
9847
+ import_path = require("path");
9951
9848
  pipeline = null;
9952
9849
  pipelineLoading = false;
9953
9850
  sqliteVecLoaded = false;
@@ -9963,7 +9860,7 @@ __export(db_exports, {
9963
9860
  });
9964
9861
  function expandTilde(p) {
9965
9862
  if (p.startsWith("~/") || p === "~") {
9966
- return p.replace("~", (0, import_os.homedir)());
9863
+ return p.replace("~", (0, import_os2.homedir)());
9967
9864
  }
9968
9865
  return p;
9969
9866
  }
@@ -9992,12 +9889,12 @@ function closeMcpDatabase() {
9992
9889
  db = null;
9993
9890
  }
9994
9891
  }
9995
- var import_better_sqlite3, import_os, db;
9892
+ var import_better_sqlite3, import_os2, db;
9996
9893
  var init_db = __esm({
9997
9894
  "src/mcp/db.ts"() {
9998
9895
  "use strict";
9999
9896
  import_better_sqlite3 = __toESM(require("better-sqlite3"));
10000
- import_os = require("os");
9897
+ import_os2 = require("os");
10001
9898
  init_db_migrations();
10002
9899
  init_db_queries();
10003
9900
  init_embeddings();
@@ -34479,9 +34376,9 @@ function registerMemoryTools(server) {
34479
34376
 
34480
34377
  // src/mcp/tools/scheduler.ts
34481
34378
  var import_path4 = require("path");
34482
- var import_os5 = require("os");
34483
- var import_fs4 = require("fs");
34484
- var import_crypto7 = require("crypto");
34379
+ var import_os4 = require("os");
34380
+ var import_fs3 = require("fs");
34381
+ var import_crypto5 = require("crypto");
34485
34382
  var import_http = require("http");
34486
34383
  var import_node_cron = __toESM(require_node_cron());
34487
34384
  init_db();
@@ -34490,13 +34387,13 @@ init_constants();
34490
34387
 
34491
34388
  // src/shared/task-runner.ts
34492
34389
  var import_path3 = require("path");
34493
- var import_fs3 = require("fs");
34390
+ var import_fs2 = require("fs");
34494
34391
 
34495
34392
  // src/shared/claude-code.ts
34496
34393
  var import_child_process = require("child_process");
34497
34394
  var import_fs = require("fs");
34498
- var import_path = require("path");
34499
- var import_os2 = require("os");
34395
+ var import_path2 = require("path");
34396
+ var import_os3 = require("os");
34500
34397
 
34501
34398
  // src/shared/process-supervisor.ts
34502
34399
  var managedChildPids = /* @__PURE__ */ new Set();
@@ -34526,7 +34423,7 @@ function resolveNodeScript(cmdPath) {
34526
34423
  const content = (0, import_fs.readFileSync)(cmdPath, "utf-8");
34527
34424
  const match = content.match(/%dp0%\\(.+?\.js)/);
34528
34425
  if (match) {
34529
- const script = (0, import_path.join)((0, import_path.dirname)(cmdPath), match[1]);
34426
+ const script = (0, import_path2.join)((0, import_path2.dirname)(cmdPath), match[1]);
34530
34427
  if ((0, import_fs.existsSync)(script)) return script;
34531
34428
  }
34532
34429
  } catch {
@@ -34535,19 +34432,19 @@ function resolveNodeScript(cmdPath) {
34535
34432
  }
34536
34433
  function resolveClaudePath() {
34537
34434
  if (cachedClaudePath) return cachedClaudePath;
34538
- const home = (0, import_os2.homedir)();
34435
+ const home = (0, import_os3.homedir)();
34539
34436
  const isWindows = process.platform === "win32";
34540
34437
  const candidates = isWindows ? [
34541
- (0, import_path.join)(home, ".claude", "bin", "claude.exe"),
34542
- (0, import_path.join)(home, "AppData", "Local", "Programs", "claude-code", "claude.exe"),
34543
- (0, import_path.join)(home, "AppData", "Local", "Claude", "claude.exe"),
34544
- (0, import_path.join)(home, "AppData", "Local", "Microsoft", "WinGet", "Links", "claude.exe"),
34438
+ (0, import_path2.join)(home, ".claude", "bin", "claude.exe"),
34439
+ (0, import_path2.join)(home, "AppData", "Local", "Programs", "claude-code", "claude.exe"),
34440
+ (0, import_path2.join)(home, "AppData", "Local", "Claude", "claude.exe"),
34441
+ (0, import_path2.join)(home, "AppData", "Local", "Microsoft", "WinGet", "Links", "claude.exe"),
34545
34442
  "C:\\Program Files\\Claude\\claude.exe",
34546
34443
  // npm global install creates .cmd wrappers, not .exe
34547
- (0, import_path.join)(home, "AppData", "Roaming", "npm", "claude.cmd")
34444
+ (0, import_path2.join)(home, "AppData", "Roaming", "npm", "claude.cmd")
34548
34445
  ] : [
34549
- (0, import_path.join)(home, ".local", "bin", "claude"),
34550
- (0, import_path.join)(home, ".claude", "bin", "claude"),
34446
+ (0, import_path2.join)(home, ".local", "bin", "claude"),
34447
+ (0, import_path2.join)(home, ".claude", "bin", "claude"),
34551
34448
  "/usr/local/bin/claude",
34552
34449
  "/usr/bin/claude",
34553
34450
  "/snap/bin/claude",
@@ -34649,14 +34546,14 @@ function executeClaudeCode(prompt, options) {
34649
34546
  const nodeScript = resolveNodeScript(claudePath);
34650
34547
  if (nodeScript) {
34651
34548
  proc = (0, import_child_process.spawn)(process.execPath, [nodeScript, ...args], {
34652
- cwd: (0, import_os2.homedir)(),
34549
+ cwd: (0, import_os3.homedir)(),
34653
34550
  env,
34654
34551
  stdio: ["ignore", "pipe", "pipe"],
34655
34552
  windowsHide: true
34656
34553
  });
34657
34554
  } else {
34658
34555
  proc = (0, import_child_process.spawn)(claudePath, args, {
34659
- cwd: (0, import_os2.homedir)(),
34556
+ cwd: (0, import_os3.homedir)(),
34660
34557
  env,
34661
34558
  stdio: ["ignore", "pipe", "pipe"],
34662
34559
  windowsHide: true,
@@ -34798,581 +34695,6 @@ function executeClaudeCode(prompt, options) {
34798
34695
  });
34799
34696
  }
34800
34697
 
34801
- // src/shared/cloud-sync.ts
34802
- var import_crypto6 = require("crypto");
34803
- var import_os4 = require("os");
34804
- var import_path2 = require("path");
34805
- var import_fs2 = require("fs");
34806
-
34807
- // src/shared/telemetry.ts
34808
- var import_crypto5 = require("crypto");
34809
- var import_os3 = require("os");
34810
- var TELEMETRY_TOKEN = process.env.QUOROOM_TELEMETRY_TOKEN ?? "";
34811
- var cachedMachineId = null;
34812
- function getMachineId() {
34813
- if (cachedMachineId) return cachedMachineId;
34814
- try {
34815
- const raw = (0, import_os3.hostname)() + (0, import_os3.userInfo)().username;
34816
- cachedMachineId = (0, import_crypto5.createHash)("sha256").update(raw).digest("hex").slice(0, 12);
34817
- } catch {
34818
- cachedMachineId = "unknown";
34819
- }
34820
- return cachedMachineId;
34821
- }
34822
-
34823
- // src/shared/cloud-sync.ts
34824
- function getCloudApi() {
34825
- return (process.env.QUOROOM_CLOUD_API ?? "https://quoroom.io/api").replace(/\/$/, "");
34826
- }
34827
- var TOKEN_FILE_NAME = "cloud-room-tokens.json";
34828
- var cachedTokens = null;
34829
- function getCloudTokenFilePath() {
34830
- const explicitDataDir = process.env.QUOROOM_DATA_DIR?.trim();
34831
- if (explicitDataDir) return (0, import_path2.join)(explicitDataDir, TOKEN_FILE_NAME);
34832
- const dbPath = process.env.QUOROOM_DB_PATH?.trim();
34833
- if (dbPath) return (0, import_path2.join)((0, import_path2.dirname)(dbPath), TOKEN_FILE_NAME);
34834
- return (0, import_path2.join)((0, import_os4.homedir)(), ".quoroom", TOKEN_FILE_NAME);
34835
- }
34836
- function loadTokenStore() {
34837
- if (cachedTokens) return cachedTokens;
34838
- const filePath = getCloudTokenFilePath();
34839
- try {
34840
- const parsed = JSON.parse((0, import_fs2.readFileSync)(filePath, "utf-8"));
34841
- cachedTokens = parsed.rooms ?? {};
34842
- } catch {
34843
- cachedTokens = {};
34844
- }
34845
- return cachedTokens;
34846
- }
34847
- function saveTokenStore() {
34848
- const filePath = getCloudTokenFilePath();
34849
- (0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
34850
- const payload = JSON.stringify({ rooms: loadTokenStore() }, null, 2) + "\n";
34851
- (0, import_fs2.writeFileSync)(filePath, payload, { mode: 384 });
34852
- }
34853
- function getRoomToken(roomId) {
34854
- return loadTokenStore()[roomId];
34855
- }
34856
- function setRoomToken(roomId, token) {
34857
- loadTokenStore()[roomId] = token;
34858
- saveTokenStore();
34859
- }
34860
- function cloudHeaders(roomId, extra = {}) {
34861
- const roomToken = roomId ? getRoomToken(roomId) : void 0;
34862
- if (!roomToken) return extra;
34863
- return { ...extra, "X-Room-Token": roomToken };
34864
- }
34865
- async function ensureCloudRoomToken(data) {
34866
- if (getRoomToken(data.roomId)) return true;
34867
- await registerWithCloud(data);
34868
- return Boolean(getRoomToken(data.roomId));
34869
- }
34870
- async function registerWithCloud(data) {
34871
- try {
34872
- const payload = {
34873
- ...data,
34874
- inviteCode: data.referredByCode ?? null,
34875
- keeperReferralCode: data.keeperReferralCode ?? null
34876
- };
34877
- const res = await fetch(`${getCloudApi()}/rooms/register`, {
34878
- method: "POST",
34879
- headers: cloudHeaders(data.roomId, { "Content-Type": "application/json" }),
34880
- body: JSON.stringify(payload),
34881
- signal: AbortSignal.timeout(1e4)
34882
- });
34883
- if (!res.ok) return;
34884
- const result = await res.json().catch(() => ({}));
34885
- if (typeof result.roomToken === "string" && result.roomToken.length > 0) {
34886
- setRoomToken(data.roomId, result.roomToken);
34887
- }
34888
- } catch {
34889
- }
34890
- }
34891
- function getRoomCloudId(dbRoomId) {
34892
- const machineId = getMachineId();
34893
- return (0, import_crypto6.createHash)("sha256").update(`${machineId}:${dbRoomId}`).digest("hex").slice(0, 32);
34894
- }
34895
- var CLOUD_STATIONS_CACHE_MS = 1e4;
34896
- var cloudStationsCache = /* @__PURE__ */ new Map();
34897
- function getCachedCloudStations(cloudRoomId) {
34898
- const cached2 = cloudStationsCache.get(cloudRoomId);
34899
- if (!cached2) return null;
34900
- if (Date.now() >= cached2.expiresAt) {
34901
- cloudStationsCache.delete(cloudRoomId);
34902
- return null;
34903
- }
34904
- return cached2.value;
34905
- }
34906
- function setCachedCloudStations(cloudRoomId, stations) {
34907
- cloudStationsCache.set(cloudRoomId, {
34908
- value: stations,
34909
- expiresAt: Date.now() + CLOUD_STATIONS_CACHE_MS
34910
- });
34911
- }
34912
- function invalidateCloudStationsCache(cloudRoomId) {
34913
- cloudStationsCache.delete(cloudRoomId);
34914
- }
34915
- async function listCloudStations(cloudRoomId) {
34916
- const cached2 = getCachedCloudStations(cloudRoomId);
34917
- if (cached2) return cached2;
34918
- try {
34919
- const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations`, {
34920
- headers: cloudHeaders(cloudRoomId),
34921
- signal: AbortSignal.timeout(1e4)
34922
- });
34923
- if (!res.ok) return [];
34924
- const data = await res.json();
34925
- const stations = data.stations ?? [];
34926
- setCachedCloudStations(cloudRoomId, stations);
34927
- return stations;
34928
- } catch {
34929
- return [];
34930
- }
34931
- }
34932
- async function execOnCloudStation(cloudRoomId, subId, command, timeoutMs = 9e4) {
34933
- try {
34934
- const res = await fetch(
34935
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/exec`,
34936
- {
34937
- method: "POST",
34938
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
34939
- body: JSON.stringify({ command }),
34940
- signal: AbortSignal.timeout(timeoutMs)
34941
- }
34942
- );
34943
- if (!res.ok) return null;
34944
- return res.json();
34945
- } catch {
34946
- return null;
34947
- }
34948
- }
34949
- async function getCloudStationLogs(cloudRoomId, subId, lines) {
34950
- try {
34951
- const query = lines ? `?lines=${lines}` : "";
34952
- const res = await fetch(
34953
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/logs${query}`,
34954
- {
34955
- headers: cloudHeaders(cloudRoomId),
34956
- signal: AbortSignal.timeout(15e3)
34957
- }
34958
- );
34959
- if (!res.ok) return null;
34960
- const data = await res.json();
34961
- return data.logs ?? "";
34962
- } catch {
34963
- return null;
34964
- }
34965
- }
34966
- async function startCloudStation(cloudRoomId, subId) {
34967
- try {
34968
- await fetch(
34969
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/start`,
34970
- {
34971
- method: "POST",
34972
- headers: cloudHeaders(cloudRoomId),
34973
- signal: AbortSignal.timeout(3e4)
34974
- }
34975
- );
34976
- } catch {
34977
- } finally {
34978
- invalidateCloudStationsCache(cloudRoomId);
34979
- }
34980
- }
34981
- async function stopCloudStation(cloudRoomId, subId) {
34982
- try {
34983
- await fetch(
34984
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/stop`,
34985
- {
34986
- method: "POST",
34987
- headers: cloudHeaders(cloudRoomId),
34988
- signal: AbortSignal.timeout(3e4)
34989
- }
34990
- );
34991
- } catch {
34992
- } finally {
34993
- invalidateCloudStationsCache(cloudRoomId);
34994
- }
34995
- }
34996
- async function deleteCloudStation(cloudRoomId, subId) {
34997
- try {
34998
- await fetch(
34999
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}`,
35000
- {
35001
- method: "DELETE",
35002
- headers: cloudHeaders(cloudRoomId),
35003
- signal: AbortSignal.timeout(3e4)
35004
- }
35005
- );
35006
- } catch {
35007
- } finally {
35008
- invalidateCloudStationsCache(cloudRoomId);
35009
- }
35010
- }
35011
- async function cancelCloudStation(cloudRoomId, subId) {
35012
- try {
35013
- await fetch(
35014
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/cancel/${subId}`,
35015
- {
35016
- method: "POST",
35017
- headers: cloudHeaders(cloudRoomId),
35018
- signal: AbortSignal.timeout(3e4)
35019
- }
35020
- );
35021
- } catch {
35022
- } finally {
35023
- invalidateCloudStationsCache(cloudRoomId);
35024
- }
35025
- }
35026
- async function getCloudCryptoPrices(cloudRoomId) {
35027
- try {
35028
- const res = await fetch(
35029
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/crypto-prices`,
35030
- { signal: AbortSignal.timeout(1e4) }
35031
- );
35032
- if (!res.ok) return null;
35033
- return res.json();
35034
- } catch {
35035
- return null;
35036
- }
35037
- }
35038
- async function getCloudOnrampUrl(cloudRoomId, walletAddress, amount) {
35039
- try {
35040
- const params = new URLSearchParams({ address: walletAddress });
35041
- if (amount) params.set("amount", String(amount));
35042
- const res = await fetch(
35043
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/onramp-url?${params}`,
35044
- { signal: AbortSignal.timeout(15e3) }
35045
- );
35046
- if (!res.ok) return null;
35047
- return res.json();
35048
- } catch {
35049
- return null;
35050
- }
35051
- }
35052
- async function cryptoCheckoutStation(cloudRoomId, tier, stationName, txHash, chain = "base") {
35053
- try {
35054
- const res = await fetch(
35055
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/crypto-checkout`,
35056
- {
35057
- method: "POST",
35058
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
35059
- body: JSON.stringify({ tier, stationName, txHash, chain }),
35060
- signal: AbortSignal.timeout(6e4)
35061
- }
35062
- );
35063
- return res.json();
35064
- } catch (err) {
35065
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
35066
- } finally {
35067
- invalidateCloudStationsCache(cloudRoomId);
35068
- }
35069
- }
35070
- async function cryptoRenewStation(cloudRoomId, subscriptionId, txHash, chain = "base") {
35071
- try {
35072
- const res = await fetch(
35073
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/crypto-renew/${subscriptionId}`,
35074
- {
35075
- method: "POST",
35076
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
35077
- body: JSON.stringify({ txHash, chain }),
35078
- signal: AbortSignal.timeout(6e4)
35079
- }
35080
- );
35081
- return res.json();
35082
- } catch (err) {
35083
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
35084
- } finally {
35085
- invalidateCloudStationsCache(cloudRoomId);
35086
- }
35087
- }
35088
- async function createCloudInvite(cloudRoomId, options) {
35089
- try {
35090
- const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/invites`, {
35091
- method: "POST",
35092
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
35093
- body: JSON.stringify(options ?? {}),
35094
- signal: AbortSignal.timeout(1e4)
35095
- });
35096
- if (!res.ok) return null;
35097
- const data = await res.json();
35098
- return data.inviteCode ? data : null;
35099
- } catch {
35100
- return null;
35101
- }
35102
- }
35103
- async function listCloudInvites(cloudRoomId) {
35104
- try {
35105
- const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/invites`, {
35106
- headers: cloudHeaders(cloudRoomId),
35107
- signal: AbortSignal.timeout(1e4)
35108
- });
35109
- if (!res.ok) return [];
35110
- const data = await res.json();
35111
- return data.invites ?? [];
35112
- } catch {
35113
- return [];
35114
- }
35115
- }
35116
- async function fetchReferredRooms(cloudRoomId) {
35117
- try {
35118
- const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/network`, {
35119
- headers: cloudHeaders(cloudRoomId),
35120
- signal: AbortSignal.timeout(1e4)
35121
- });
35122
- if (!res.ok) return [];
35123
- const data = await res.json();
35124
- return data.referredRooms ?? [];
35125
- } catch {
35126
- return [];
35127
- }
35128
- }
35129
-
35130
- // src/shared/agent-executor.ts
35131
- function resolveOpenAiCompatible(model, apiKeyOverride) {
35132
- const trimmed = model.trim();
35133
- if (trimmed === "gemini" || trimmed.startsWith("gemini:")) {
35134
- const apiKey2 = apiKeyOverride?.trim() || (process.env.GEMINI_API_KEY || "").trim();
35135
- if (!apiKey2) return null;
35136
- return {
35137
- apiKey: apiKey2,
35138
- url: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
35139
- defaultModel: "gemini-2.5-flash",
35140
- label: "Gemini",
35141
- prefix: "gemini"
35142
- };
35143
- }
35144
- const apiKey = apiKeyOverride?.trim() || (process.env.OPENAI_API_KEY || "").trim();
35145
- if (!apiKey) return null;
35146
- return {
35147
- apiKey,
35148
- url: "https://api.openai.com/v1/chat/completions",
35149
- defaultModel: "gpt-4o-mini",
35150
- label: "OpenAI",
35151
- prefix: "openai"
35152
- };
35153
- }
35154
- function parseModelSuffix(model, prefix) {
35155
- const trimmed = model.trim();
35156
- if (trimmed === prefix) return "";
35157
- const marker = `${prefix}:`;
35158
- if (!trimmed.startsWith(marker)) return "";
35159
- return trimmed.slice(marker.length).trim();
35160
- }
35161
- function parseAnthropicModel(model) {
35162
- const normalized = model.trim();
35163
- if (normalized === "anthropic") return "claude-3-5-sonnet-latest";
35164
- const anthropicModel = parseModelSuffix(normalized, "anthropic");
35165
- if (anthropicModel) return anthropicModel;
35166
- const claudeApiModel = parseModelSuffix(normalized, "claude-api");
35167
- if (claudeApiModel) return claudeApiModel;
35168
- return normalized;
35169
- }
35170
- function extractOpenAiText(json) {
35171
- const choices = json.choices;
35172
- if (Array.isArray(choices) && choices.length > 0) {
35173
- const first = choices[0];
35174
- const message = first.message;
35175
- const content = message?.content;
35176
- if (typeof content === "string") return content.trim();
35177
- if (Array.isArray(content)) {
35178
- const text = content.map((item) => {
35179
- const block = item;
35180
- const blockText = block.text;
35181
- return typeof blockText === "string" ? blockText : "";
35182
- }).filter(Boolean).join("\n").trim();
35183
- if (text) return text;
35184
- }
35185
- }
35186
- return JSON.stringify(json);
35187
- }
35188
- function extractAnthropicText(json) {
35189
- const content = json.content;
35190
- if (Array.isArray(content)) {
35191
- const text = content.map((item) => {
35192
- const block = item;
35193
- return block.type === "text" && typeof block.text === "string" ? block.text : "";
35194
- }).filter(Boolean).join("\n").trim();
35195
- if (text) return text;
35196
- }
35197
- return JSON.stringify(json);
35198
- }
35199
- function immediateError(message) {
35200
- return {
35201
- output: `Error: ${message}`,
35202
- exitCode: 1,
35203
- durationMs: 0,
35204
- sessionId: null,
35205
- timedOut: false
35206
- };
35207
- }
35208
- async function executeApiOnStation(cloudRoomId, stationId, options) {
35209
- const startTime = Date.now();
35210
- const apiKey = options.apiKey;
35211
- if (!apiKey) {
35212
- return immediateError("Missing API key for station execution.");
35213
- }
35214
- if (options.abortSignal?.aborted) {
35215
- return {
35216
- output: "Execution aborted",
35217
- exitCode: 130,
35218
- durationMs: Date.now() - startTime,
35219
- sessionId: null,
35220
- timedOut: false
35221
- };
35222
- }
35223
- const isOpenAiCompat = options.model.startsWith("openai:") || options.model.startsWith("gemini:");
35224
- const messages = [];
35225
- if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
35226
- messages.push({ role: "user", content: options.prompt });
35227
- let url;
35228
- let headers;
35229
- let body;
35230
- if (isOpenAiCompat) {
35231
- const config2 = resolveOpenAiCompatible(options.model, apiKey);
35232
- const modelName = config2 ? parseModelSuffix(options.model, config2.prefix) || config2.defaultModel : parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
35233
- url = config2?.url ?? "https://api.openai.com/v1/chat/completions";
35234
- headers = { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" };
35235
- body = JSON.stringify({ model: modelName, messages });
35236
- } else {
35237
- const modelName = parseAnthropicModel(options.model);
35238
- url = "https://api.anthropic.com/v1/messages";
35239
- headers = { "x-api-key": apiKey, "anthropic-version": "2023-06-01", "content-type": "application/json" };
35240
- body = JSON.stringify({
35241
- model: modelName,
35242
- max_tokens: 2048,
35243
- system: options.systemPrompt,
35244
- messages: [{ role: "user", content: options.prompt }]
35245
- });
35246
- }
35247
- const headerFlags = Object.entries(headers).map(([k, v]) => `-H '${k}: ${v}'`).join(" ");
35248
- const b64 = Buffer.from(body).toString("base64");
35249
- const command = `echo '${b64}' | base64 -d | curl -s --max-time 300 ${headerFlags} -d @- '${url}'`;
35250
- const result = await new Promise((resolve) => {
35251
- let settled = false;
35252
- const finish = (value) => {
35253
- if (settled) return;
35254
- settled = true;
35255
- resolve(value);
35256
- };
35257
- const onAbort = () => finish({ stdout: "", stderr: "Execution aborted", exitCode: 130 });
35258
- if (options.abortSignal) {
35259
- if (options.abortSignal.aborted) {
35260
- onAbort();
35261
- return;
35262
- }
35263
- options.abortSignal.addEventListener("abort", onAbort, { once: true });
35264
- }
35265
- void execOnCloudStation(cloudRoomId, stationId, command, 36e4).then((value) => finish(value)).catch(() => finish(null)).finally(() => {
35266
- if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
35267
- });
35268
- });
35269
- if (!result) {
35270
- return {
35271
- output: "Error: station execution failed (station unreachable)",
35272
- exitCode: 1,
35273
- durationMs: Date.now() - startTime,
35274
- sessionId: null,
35275
- timedOut: false
35276
- };
35277
- }
35278
- if (result.exitCode !== 0) {
35279
- return {
35280
- output: result.stderr || result.stdout || `Station exec failed with exit code ${result.exitCode}`,
35281
- exitCode: result.exitCode,
35282
- durationMs: Date.now() - startTime,
35283
- sessionId: null,
35284
- timedOut: false
35285
- };
35286
- }
35287
- try {
35288
- const parsed = JSON.parse(result.stdout);
35289
- const output = isOpenAiCompat ? extractOpenAiText(parsed) : extractAnthropicText(parsed);
35290
- return { output, exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
35291
- } catch {
35292
- return {
35293
- output: result.stdout || "(no output from API)",
35294
- exitCode: 1,
35295
- durationMs: Date.now() - startTime,
35296
- sessionId: null,
35297
- timedOut: false
35298
- };
35299
- }
35300
- }
35301
-
35302
- // src/shared/model-provider.ts
35303
- init_db_queries();
35304
- function normalizeModel(model) {
35305
- const trimmed = model?.trim();
35306
- return trimmed ? trimmed : "claude";
35307
- }
35308
- function getModelProvider(model) {
35309
- const normalized = normalizeModel(model);
35310
- if (normalized === "codex" || normalized.startsWith("codex:")) return "codex_subscription";
35311
- if (normalized === "openai" || normalized.startsWith("openai:")) return "openai_api";
35312
- if (normalized === "anthropic" || normalized.startsWith("anthropic:") || normalized.startsWith("claude-api:")) {
35313
- return "anthropic_api";
35314
- }
35315
- if (normalized === "gemini" || normalized.startsWith("gemini:")) return "gemini_api";
35316
- return "claude_subscription";
35317
- }
35318
- function resolveApiKeyForModel(db2, roomId, model) {
35319
- const provider = getModelProvider(model);
35320
- if (provider === "openai_api") {
35321
- return resolveApiKey(db2, roomId, "openai_api_key", "OPENAI_API_KEY");
35322
- }
35323
- if (provider === "anthropic_api") {
35324
- return resolveApiKey(db2, roomId, "anthropic_api_key", "ANTHROPIC_API_KEY");
35325
- }
35326
- if (provider === "gemini_api") {
35327
- return resolveApiKey(db2, roomId, "gemini_api_key", "GEMINI_API_KEY");
35328
- }
35329
- return void 0;
35330
- }
35331
- function resolveApiKey(db2, roomId, credentialName, envVar) {
35332
- const roomCred = getRoomCredential(db2, roomId, credentialName);
35333
- if (roomCred) return roomCred;
35334
- const sharedRoomCred = findAnyRoomCredential(db2, credentialName, roomId);
35335
- if (sharedRoomCred) return sharedRoomCred;
35336
- const clerkCred = getClerkCredential(db2, credentialName);
35337
- if (clerkCred) return clerkCred;
35338
- return getEnvValue(envVar) || void 0;
35339
- }
35340
- function findAnyRoomCredential(db2, credentialName, excludeRoomId) {
35341
- const rooms = listRooms(db2);
35342
- for (const room of rooms) {
35343
- if (excludeRoomId != null && room.id === excludeRoomId) continue;
35344
- const value = getRoomCredential(db2, room.id, credentialName);
35345
- if (value) return value;
35346
- }
35347
- return null;
35348
- }
35349
- function getClerkCredential(db2, credentialName) {
35350
- if (credentialName === "openai_api_key") {
35351
- return getClerkApiKey(db2, "openai_api");
35352
- }
35353
- if (credentialName === "anthropic_api_key") {
35354
- return getClerkApiKey(db2, "anthropic_api");
35355
- }
35356
- if (credentialName === "gemini_api_key") {
35357
- return getClerkApiKey(db2, "gemini_api");
35358
- }
35359
- return null;
35360
- }
35361
- function getRoomCredential(db2, roomId, credentialName) {
35362
- try {
35363
- const credential = getCredentialByName(db2, roomId, credentialName);
35364
- if (!credential) return null;
35365
- const value = (credential.valueEncrypted || "").trim();
35366
- if (!value || value.startsWith("enc:v1:")) return null;
35367
- return value;
35368
- } catch {
35369
- return null;
35370
- }
35371
- }
35372
- function getEnvValue(envVar) {
35373
- return (process.env[envVar] || "").trim();
35374
- }
35375
-
35376
34698
  // src/shared/task-runner.ts
35377
34699
  init_db_queries();
35378
34700
  init_constants();
@@ -35787,78 +35109,6 @@ async function executeTask(taskId, options) {
35787
35109
  } catch (err) {
35788
35110
  console.warn("Non-fatal: worker resolution failed:", err);
35789
35111
  }
35790
- const isStationModel = model?.startsWith("openai:") || model?.startsWith("anthropic:") || model?.startsWith("claude-api:");
35791
- if (isStationModel && task.roomId) {
35792
- runningTasks.add(taskId);
35793
- taskAbortControllers.set(taskId, taskAbort);
35794
- try {
35795
- const cloudRoomId = getRoomCloudId(task.roomId);
35796
- const stations = await listCloudStations(cloudRoomId);
35797
- const activeStations = stations.filter((s) => s.status === "active");
35798
- if (activeStations.length > 0) {
35799
- const run2 = createTaskRun(db2, taskId);
35800
- try {
35801
- const station = activeStations[run2.id % activeStations.length];
35802
- let augmentedPrompt = prependKeeperReferral(task.prompt, db2);
35803
- try {
35804
- if (task.learnedContext) {
35805
- augmentedPrompt = `## Approach (learned from previous runs):
35806
- ${task.learnedContext}
35807
-
35808
- ---
35809
-
35810
- ${augmentedPrompt}`;
35811
- }
35812
- } catch (err) {
35813
- console.warn("Non-fatal: learned context injection failed:", err);
35814
- }
35815
- try {
35816
- const memoryContext = getTaskMemoryContext(db2, taskId);
35817
- if (memoryContext) {
35818
- augmentedPrompt = `${memoryContext}
35819
-
35820
- ---
35821
-
35822
- ${augmentedPrompt}`;
35823
- }
35824
- } catch (err) {
35825
- console.warn("Non-fatal: memory injection failed:", err);
35826
- }
35827
- const timeoutMs = task.timeoutMinutes != null ? task.timeoutMinutes * 60 * 1e3 : void 0;
35828
- const stationModel = model;
35829
- const apiKey = resolveApiKeyForModel(db2, task.roomId, stationModel);
35830
- const agentResult = await executeApiOnStation(cloudRoomId, station.id, {
35831
- model: stationModel,
35832
- prompt: augmentedPrompt,
35833
- systemPrompt,
35834
- timeoutMs,
35835
- apiKey,
35836
- abortSignal: taskAbort.signal
35837
- });
35838
- const result = agentResultToExecutionResult(agentResult);
35839
- if (taskAbort.signal.aborted) {
35840
- const errorMsg = "Execution aborted";
35841
- completeTaskRun(db2, run2.id, result.stdout || errorMsg, void 0, errorMsg);
35842
- onFailed?.(task, errorMsg);
35843
- return { success: false, output: result.stdout || "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
35844
- }
35845
- return finishRun(db2, run2.id, taskId, task, result, resultsDir, onComplete, onFailed);
35846
- } catch (err) {
35847
- const errorMsg = taskAbort.signal.aborted ? "Execution aborted" : err instanceof Error ? err.message : String(err);
35848
- completeTaskRun(db2, run2.id, "", void 0, errorMsg);
35849
- onFailed?.(task, errorMsg);
35850
- return { success: false, output: "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
35851
- }
35852
- }
35853
- } catch (err) {
35854
- const errorMsg = taskAbort.signal.aborted ? "Execution aborted" : err instanceof Error ? err.message : String(err);
35855
- onFailed?.(task, errorMsg);
35856
- return { success: false, output: "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
35857
- } finally {
35858
- runningTasks.delete(taskId);
35859
- taskAbortControllers.delete(taskId);
35860
- }
35861
- }
35862
35112
  await acquireSlot(getMaxConcurrentTasks(db2, task.roomId));
35863
35113
  runningTasks.add(taskId);
35864
35114
  taskAbortControllers.set(taskId, taskAbort);
@@ -36033,16 +35283,6 @@ ${retryPrompt}`;
36033
35283
  releaseSlot();
36034
35284
  }
36035
35285
  }
36036
- function agentResultToExecutionResult(result) {
36037
- return {
36038
- stdout: result.output,
36039
- stderr: "",
36040
- exitCode: result.exitCode,
36041
- durationMs: result.durationMs,
36042
- timedOut: result.timedOut,
36043
- sessionId: result.sessionId
36044
- };
36045
- }
36046
35286
  function finishRun(db2, runId, taskId, task, result, resultsDir, onComplete, onFailed) {
36047
35287
  const output = result.stdout || result.stderr || "(no output)";
36048
35288
  const resultFilePath = saveResult(resultsDir, task.name, output, result);
@@ -36098,8 +35338,8 @@ function finishRun(db2, runId, taskId, task, result, resultsDir, onComplete, onF
36098
35338
  }
36099
35339
  }
36100
35340
  function saveResult(resultsDir, taskName, output, result) {
36101
- if (!(0, import_fs3.existsSync)(resultsDir)) {
36102
- (0, import_fs3.mkdirSync)(resultsDir, { recursive: true });
35341
+ if (!(0, import_fs2.existsSync)(resultsDir)) {
35342
+ (0, import_fs2.mkdirSync)(resultsDir, { recursive: true });
36103
35343
  }
36104
35344
  const safeName = taskName.replace(/[^a-zA-Z0-9-_]/g, "_").substring(0, 50);
36105
35345
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -36115,7 +35355,7 @@ function saveResult(resultsDir, taskName, output, result) {
36115
35355
 
36116
35356
  ${output}
36117
35357
  `;
36118
- (0, import_fs3.writeFileSync)(filePath, markdown, "utf-8");
35358
+ (0, import_fs2.writeFileSync)(filePath, markdown, "utf-8");
36119
35359
  return filePath;
36120
35360
  }
36121
35361
 
@@ -36123,10 +35363,10 @@ ${output}
36123
35363
  function getServerPort() {
36124
35364
  try {
36125
35365
  const dbPath = process.env.QUOROOM_DB_PATH;
36126
- const dataDir = process.env.QUOROOM_DATA_DIR || (dbPath ? (0, import_path4.dirname)(dbPath) : (0, import_path4.join)((0, import_os5.homedir)(), `.${APP_NAME.toLowerCase()}`));
35366
+ const dataDir = process.env.QUOROOM_DATA_DIR || (dbPath ? (0, import_path4.dirname)(dbPath) : (0, import_path4.join)((0, import_os4.homedir)(), `.${APP_NAME.toLowerCase()}`));
36127
35367
  const portFile = (0, import_path4.join)(dataDir, "api.port");
36128
- if ((0, import_fs4.existsSync)(portFile)) {
36129
- const port = parseInt((0, import_fs4.readFileSync)(portFile, "utf-8").trim(), 10);
35368
+ if ((0, import_fs3.existsSync)(portFile)) {
35369
+ const port = parseInt((0, import_fs3.readFileSync)(portFile, "utf-8").trim(), 10);
36130
35370
  return Number.isFinite(port) && port > 0 ? port : null;
36131
35371
  }
36132
35372
  } catch {
@@ -36235,7 +35475,7 @@ function registerSchedulerTools(server) {
36235
35475
  };
36236
35476
  }
36237
35477
  }
36238
- const webhookToken = triggerType === TRIGGER_TYPES.WEBHOOK ? (0, import_crypto7.randomBytes)(16).toString("hex") : void 0;
35478
+ const webhookToken = triggerType === TRIGGER_TYPES.WEBHOOK ? (0, import_crypto5.randomBytes)(16).toString("hex") : void 0;
36239
35479
  const task = createTask(db2, {
36240
35480
  name: taskName,
36241
35481
  prompt,
@@ -36364,7 +35604,7 @@ Trigger it with: curl -X POST ${webhookUrl}`
36364
35604
  if (task.status !== TASK_STATUSES.ACTIVE) {
36365
35605
  updateTask(db2, id, { status: TASK_STATUSES.ACTIVE });
36366
35606
  }
36367
- const resultsDir = process.env.QUOROOM_RESULTS_DIR || (0, import_path4.join)((0, import_os5.homedir)(), APP_NAME, "results");
35607
+ const resultsDir = process.env.QUOROOM_RESULTS_DIR || (0, import_path4.join)((0, import_os4.homedir)(), APP_NAME, "results");
36368
35608
  executeTask(id, { db: db2, resultsDir }).then((result) => {
36369
35609
  if (originalStatus !== TASK_STATUSES.ACTIVE) {
36370
35610
  const currentTask = getTask(db2, id);
@@ -36379,7 +35619,7 @@ Trigger it with: curl -X POST ${webhookUrl}`
36379
35619
  const dbPath = process.env.QUOROOM_DB_PATH;
36380
35620
  if (dbPath) {
36381
35621
  const dataDir = process.env.QUOROOM_DATA_DIR || (0, import_path4.dirname)(dbPath);
36382
- const port = parseInt((0, import_fs4.readFileSync)((0, import_path4.join)(dataDir, "sidecar.port"), "utf-8").trim(), 10);
35622
+ const port = parseInt((0, import_fs3.readFileSync)((0, import_path4.join)(dataDir, "sidecar.port"), "utf-8").trim(), 10);
36383
35623
  if (port > 0) {
36384
35624
  const event = result.success ? "task:complete" : "task:failed";
36385
35625
  const payload = JSON.stringify({
@@ -36660,7 +35900,7 @@ Trigger it with: curl -X POST ${webhookUrl}`
36660
35900
  }
36661
35901
  let token = task.webhookToken;
36662
35902
  if (!token && generateIfMissing) {
36663
- token = (0, import_crypto7.randomBytes)(16).toString("hex");
35903
+ token = (0, import_crypto5.randomBytes)(16).toString("hex");
36664
35904
  updateTask(db2, taskId, { webhookToken: token });
36665
35905
  }
36666
35906
  if (!token) {
@@ -36687,7 +35927,7 @@ With payload: curl -X POST ${url} -H "Content-Type: application/json" -d '{"mess
36687
35927
  }
36688
35928
  let token = room.webhookToken;
36689
35929
  if (!token && generateIfMissing) {
36690
- token = (0, import_crypto7.randomBytes)(16).toString("hex");
35930
+ token = (0, import_crypto5.randomBytes)(16).toString("hex");
36691
35931
  updateRoom(db2, roomId, { webhookToken: token });
36692
35932
  }
36693
35933
  if (!token) {
@@ -36897,7 +36137,7 @@ init_db();
36897
36137
  init_db_queries();
36898
36138
 
36899
36139
  // src/shared/room.ts
36900
- var import_crypto10 = __toESM(require("crypto"));
36140
+ var import_crypto8 = __toESM(require("crypto"));
36901
36141
  init_db_queries();
36902
36142
  init_constants();
36903
36143
 
@@ -36943,7 +36183,7 @@ function getGoalTree(db2, roomId) {
36943
36183
  }
36944
36184
 
36945
36185
  // src/shared/wallet.ts
36946
- var import_crypto9 = __toESM(require("crypto"));
36186
+ var import_crypto7 = __toESM(require("crypto"));
36947
36187
 
36948
36188
  // node_modules/viem/_esm/actions/public/getTransactionCount.js
36949
36189
  init_fromHex();
@@ -46137,21 +45377,21 @@ var VIEM_CHAINS = {
46137
45377
  var ENCRYPTION_ALGORITHM = "aes-256-gcm";
46138
45378
  var IV_LENGTH = 12;
46139
45379
  function encryptPrivateKey(privateKey, encryptionKey) {
46140
- const key = typeof encryptionKey === "string" ? import_crypto9.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
46141
- const iv = import_crypto9.default.randomBytes(IV_LENGTH);
46142
- const cipher = import_crypto9.default.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
45380
+ const key = typeof encryptionKey === "string" ? import_crypto7.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
45381
+ const iv = import_crypto7.default.randomBytes(IV_LENGTH);
45382
+ const cipher = import_crypto7.default.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
46143
45383
  const encrypted = Buffer.concat([cipher.update(privateKey, "utf8"), cipher.final()]);
46144
45384
  const tag = cipher.getAuthTag();
46145
45385
  return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`;
46146
45386
  }
46147
45387
  function decryptPrivateKey(encrypted, encryptionKey) {
46148
- const key = typeof encryptionKey === "string" ? import_crypto9.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
45388
+ const key = typeof encryptionKey === "string" ? import_crypto7.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
46149
45389
  const parts = encrypted.split(":");
46150
45390
  if (parts.length !== 3) throw new Error("Invalid encrypted key format");
46151
45391
  const iv = Buffer.from(parts[0], "hex");
46152
45392
  const tag = Buffer.from(parts[1], "hex");
46153
45393
  const ciphertext = Buffer.from(parts[2], "hex");
46154
- const decipher = import_crypto9.default.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
45394
+ const decipher = import_crypto7.default.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
46155
45395
  decipher.setAuthTag(tag);
46156
45396
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
46157
45397
  }
@@ -46286,7 +45526,7 @@ function createRoom2(db2, input) {
46286
45526
  if (input.goal) {
46287
45527
  rootGoal = setRoomObjective(db2, room.id, input.goal);
46288
45528
  }
46289
- const encryptionKey = import_crypto10.default.createHash("sha256").update(`quoroom-wallet-${room.id}-${room.name}`).digest("hex");
45529
+ const encryptionKey = import_crypto8.default.createHash("sha256").update(`quoroom-wallet-${room.id}-${room.name}`).digest("hex");
46290
45530
  const wallet = createRoomWallet(db2, room.id, encryptionKey);
46291
45531
  logRoomActivity(
46292
45532
  db2,
@@ -46714,20 +45954,20 @@ function vote(db2, decisionId, workerId, voteValue, reasoning) {
46714
45954
  }
46715
45955
 
46716
45956
  // src/mcp/nudge.ts
46717
- var import_fs5 = require("fs");
45957
+ var import_fs4 = require("fs");
46718
45958
  var import_path5 = require("path");
46719
- var import_os6 = require("os");
45959
+ var import_os5 = require("os");
46720
45960
  var import_http4 = require("http");
46721
45961
  init_constants();
46722
45962
  function getApiInfo() {
46723
45963
  try {
46724
45964
  const dbPath = process.env.QUOROOM_DB_PATH;
46725
- const dataDir = process.env.QUOROOM_DATA_DIR || (dbPath ? (0, import_path5.join)(dbPath, "..") : (0, import_path5.join)((0, import_os6.homedir)(), `.${APP_NAME.toLowerCase()}`));
45965
+ const dataDir = process.env.QUOROOM_DATA_DIR || (dbPath ? (0, import_path5.join)(dbPath, "..") : (0, import_path5.join)((0, import_os5.homedir)(), `.${APP_NAME.toLowerCase()}`));
46726
45966
  const portFile = (0, import_path5.join)(dataDir, "api.port");
46727
45967
  const tokenFile = (0, import_path5.join)(dataDir, "api.token");
46728
- if (!(0, import_fs5.existsSync)(portFile) || !(0, import_fs5.existsSync)(tokenFile)) return null;
46729
- const port = parseInt((0, import_fs5.readFileSync)(portFile, "utf-8").trim(), 10);
46730
- const token = (0, import_fs5.readFileSync)(tokenFile, "utf-8").trim();
45968
+ if (!(0, import_fs4.existsSync)(portFile) || !(0, import_fs4.existsSync)(tokenFile)) return null;
45969
+ const port = parseInt((0, import_fs4.readFileSync)(portFile, "utf-8").trim(), 10);
45970
+ const token = (0, import_fs4.readFileSync)(tokenFile, "utf-8").trim();
46731
45971
  if (!Number.isFinite(port) || port <= 0 || !token) return null;
46732
45972
  return { port, token };
46733
45973
  } catch {
@@ -47535,6 +46775,158 @@ function formatPaymentAuditSuffix(audit) {
47535
46775
 
47536
46776
  // src/mcp/tools/wallet.ts
47537
46777
  init_db_queries();
46778
+
46779
+ // src/shared/cloud-sync.ts
46780
+ var import_crypto10 = require("crypto");
46781
+ var import_os7 = require("os");
46782
+ var import_path6 = require("path");
46783
+ var import_fs5 = require("fs");
46784
+
46785
+ // src/shared/telemetry.ts
46786
+ var import_crypto9 = require("crypto");
46787
+ var import_os6 = require("os");
46788
+ var TELEMETRY_TOKEN = process.env.QUOROOM_TELEMETRY_TOKEN ?? "";
46789
+ var cachedMachineId = null;
46790
+ function getMachineId() {
46791
+ if (cachedMachineId) return cachedMachineId;
46792
+ try {
46793
+ const raw = (0, import_os6.hostname)() + (0, import_os6.userInfo)().username;
46794
+ cachedMachineId = (0, import_crypto9.createHash)("sha256").update(raw).digest("hex").slice(0, 12);
46795
+ } catch {
46796
+ cachedMachineId = "unknown";
46797
+ }
46798
+ return cachedMachineId;
46799
+ }
46800
+
46801
+ // src/shared/cloud-sync.ts
46802
+ function getCloudApi() {
46803
+ return (process.env.QUOROOM_CLOUD_API ?? "https://quoroom.io/api").replace(/\/$/, "");
46804
+ }
46805
+ var TOKEN_FILE_NAME = "cloud-room-tokens.json";
46806
+ var cachedTokens = null;
46807
+ function getCloudTokenFilePath() {
46808
+ const explicitDataDir = process.env.QUOROOM_DATA_DIR?.trim();
46809
+ if (explicitDataDir) return (0, import_path6.join)(explicitDataDir, TOKEN_FILE_NAME);
46810
+ const dbPath = process.env.QUOROOM_DB_PATH?.trim();
46811
+ if (dbPath) return (0, import_path6.join)((0, import_path6.dirname)(dbPath), TOKEN_FILE_NAME);
46812
+ return (0, import_path6.join)((0, import_os7.homedir)(), ".quoroom", TOKEN_FILE_NAME);
46813
+ }
46814
+ function loadTokenStore() {
46815
+ if (cachedTokens) return cachedTokens;
46816
+ const filePath = getCloudTokenFilePath();
46817
+ try {
46818
+ const parsed = JSON.parse((0, import_fs5.readFileSync)(filePath, "utf-8"));
46819
+ cachedTokens = parsed.rooms ?? {};
46820
+ } catch {
46821
+ cachedTokens = {};
46822
+ }
46823
+ return cachedTokens;
46824
+ }
46825
+ function saveTokenStore() {
46826
+ const filePath = getCloudTokenFilePath();
46827
+ (0, import_fs5.mkdirSync)((0, import_path6.dirname)(filePath), { recursive: true });
46828
+ const payload = JSON.stringify({ rooms: loadTokenStore() }, null, 2) + "\n";
46829
+ (0, import_fs5.writeFileSync)(filePath, payload, { mode: 384 });
46830
+ }
46831
+ function getRoomToken(roomId) {
46832
+ return loadTokenStore()[roomId];
46833
+ }
46834
+ function setRoomToken(roomId, token) {
46835
+ loadTokenStore()[roomId] = token;
46836
+ saveTokenStore();
46837
+ }
46838
+ function cloudHeaders(roomId, extra = {}) {
46839
+ const roomToken = roomId ? getRoomToken(roomId) : void 0;
46840
+ if (!roomToken) return extra;
46841
+ return { ...extra, "X-Room-Token": roomToken };
46842
+ }
46843
+ async function ensureCloudRoomToken(data) {
46844
+ if (getRoomToken(data.roomId)) return true;
46845
+ await registerWithCloud(data);
46846
+ return Boolean(getRoomToken(data.roomId));
46847
+ }
46848
+ async function registerWithCloud(data) {
46849
+ try {
46850
+ const payload = {
46851
+ ...data,
46852
+ inviteCode: data.referredByCode ?? null,
46853
+ keeperReferralCode: data.keeperReferralCode ?? null
46854
+ };
46855
+ const res = await fetch(`${getCloudApi()}/rooms/register`, {
46856
+ method: "POST",
46857
+ headers: cloudHeaders(data.roomId, { "Content-Type": "application/json" }),
46858
+ body: JSON.stringify(payload),
46859
+ signal: AbortSignal.timeout(1e4)
46860
+ });
46861
+ if (!res.ok) return;
46862
+ const result = await res.json().catch(() => ({}));
46863
+ if (typeof result.roomToken === "string" && result.roomToken.length > 0) {
46864
+ setRoomToken(data.roomId, result.roomToken);
46865
+ }
46866
+ } catch {
46867
+ }
46868
+ }
46869
+ function getRoomCloudId(dbRoomId) {
46870
+ const machineId = getMachineId();
46871
+ return (0, import_crypto10.createHash)("sha256").update(`${machineId}:${dbRoomId}`).digest("hex").slice(0, 32);
46872
+ }
46873
+ async function getCloudOnrampUrl(cloudRoomId, walletAddress, amount) {
46874
+ try {
46875
+ const params = new URLSearchParams({ address: walletAddress });
46876
+ if (amount) params.set("amount", String(amount));
46877
+ const res = await fetch(
46878
+ `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/onramp-url?${params}`,
46879
+ { signal: AbortSignal.timeout(15e3) }
46880
+ );
46881
+ if (!res.ok) return null;
46882
+ return res.json();
46883
+ } catch {
46884
+ return null;
46885
+ }
46886
+ }
46887
+ async function createCloudInvite(cloudRoomId, options) {
46888
+ try {
46889
+ const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/invites`, {
46890
+ method: "POST",
46891
+ headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
46892
+ body: JSON.stringify(options ?? {}),
46893
+ signal: AbortSignal.timeout(1e4)
46894
+ });
46895
+ if (!res.ok) return null;
46896
+ const data = await res.json();
46897
+ return data.inviteCode ? data : null;
46898
+ } catch {
46899
+ return null;
46900
+ }
46901
+ }
46902
+ async function listCloudInvites(cloudRoomId) {
46903
+ try {
46904
+ const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/invites`, {
46905
+ headers: cloudHeaders(cloudRoomId),
46906
+ signal: AbortSignal.timeout(1e4)
46907
+ });
46908
+ if (!res.ok) return [];
46909
+ const data = await res.json();
46910
+ return data.invites ?? [];
46911
+ } catch {
46912
+ return [];
46913
+ }
46914
+ }
46915
+ async function fetchReferredRooms(cloudRoomId) {
46916
+ try {
46917
+ const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/network`, {
46918
+ headers: cloudHeaders(cloudRoomId),
46919
+ signal: AbortSignal.timeout(1e4)
46920
+ });
46921
+ if (!res.ok) return [];
46922
+ const data = await res.json();
46923
+ return data.referredRooms ?? [];
46924
+ } catch {
46925
+ return [];
46926
+ }
46927
+ }
46928
+
46929
+ // src/mcp/tools/wallet.ts
47538
46930
  function registerWalletTools(server) {
47539
46931
  server.registerTool(
47540
46932
  "quoroom_wallet_create",
@@ -47721,428 +47113,6 @@ function registerWalletTools(server) {
47721
47113
  );
47722
47114
  }
47723
47115
 
47724
- // src/mcp/tools/station.ts
47725
- init_db();
47726
- init_db_queries();
47727
- init_constants();
47728
- var CLOUD_BASE = "https://quoroom.io";
47729
- var CLOUD_ONLY = { content: [{ type: "text", text: "Station rental is only available for cloud swarms on quoroom.io." }] };
47730
- var isCloudMode = () => process.env.QUOROOM_DEPLOYMENT_MODE === "cloud";
47731
- async function bootstrapRoomToken(roomId) {
47732
- const db2 = getMcpDatabase();
47733
- const room = getRoom(db2, roomId);
47734
- if (!room) return;
47735
- await ensureCloudRoomToken({
47736
- roomId: getRoomCloudId(roomId),
47737
- name: room.name,
47738
- goal: room.goal ?? null,
47739
- visibility: room.visibility,
47740
- referredByCode: room.referredByCode,
47741
- keeperReferralCode: getSetting(db2, "keeper_referral_code")
47742
- });
47743
- }
47744
- function registerStationTools(server) {
47745
- server.registerTool(
47746
- "quoroom_station_create",
47747
- {
47748
- title: "Create Station",
47749
- description: "Rent a cloud server (station) for the room via quoroom.io. Returns a payment URL \u2014 open it in a browser to subscribe with Stripe. For crypto payment (USDC or USDT), use quoroom_station_create_crypto instead. The station appears in ~30 seconds after payment. RESPONSE STYLE: Confirm briefly in 1 sentence, include the URL.",
47750
- inputSchema: {
47751
- roomId: external_exports.number().describe("The room ID"),
47752
- name: external_exports.string().min(1).max(100).describe('Station name (e.g., "web-server", "scraper-01")'),
47753
- tier: external_exports.enum(["micro", "small", "medium", "large"]).describe(
47754
- "Station tier: micro ($5/mo, 1 vCPU, 256 MB), small ($15/mo, 2 vCPU, 2 GB), medium ($40/mo, 2 vCPU perf, 4 GB), large ($100/mo, 4 vCPU perf, 8 GB)"
47755
- )
47756
- }
47757
- },
47758
- async ({ roomId }) => {
47759
- if (!isCloudMode()) return CLOUD_ONLY;
47760
- await bootstrapRoomToken(roomId);
47761
- const cloudRoomId = getRoomCloudId(roomId);
47762
- const url = `${CLOUD_BASE}/stations?room=${encodeURIComponent(cloudRoomId)}`;
47763
- return {
47764
- content: [{
47765
- type: "text",
47766
- text: `To add a station, complete payment at: ${url}
47767
-
47768
- The station will appear in your room within ~30 seconds after payment.`
47769
- }]
47770
- };
47771
- }
47772
- );
47773
- server.registerTool(
47774
- "quoroom_station_list",
47775
- {
47776
- title: "List Stations",
47777
- description: "List all stations for the room, optionally filtered by status.",
47778
- inputSchema: {
47779
- roomId: external_exports.number().describe("The room ID"),
47780
- status: external_exports.enum(["pending", "active", "stopped", "canceling", "past_due", "error"]).optional().describe("Filter by status")
47781
- }
47782
- },
47783
- async ({ roomId, status }) => {
47784
- if (!isCloudMode()) return CLOUD_ONLY;
47785
- await bootstrapRoomToken(roomId);
47786
- const cloudRoomId = getRoomCloudId(roomId);
47787
- const stations = await listCloudStations(cloudRoomId);
47788
- const filtered = status ? stations.filter((s) => s.status === status) : stations;
47789
- if (filtered.length === 0) {
47790
- return { content: [{ type: "text", text: "No stations found." }] };
47791
- }
47792
- const list = filtered.map((s) => ({
47793
- id: s.id,
47794
- name: s.stationName,
47795
- tier: s.tier,
47796
- status: s.status,
47797
- monthlyCost: s.monthlyCost,
47798
- createdAt: s.createdAt
47799
- }));
47800
- return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
47801
- }
47802
- );
47803
- server.registerTool(
47804
- "quoroom_station_start",
47805
- {
47806
- title: "Start Station",
47807
- description: "Start a stopped station. RESPONSE STYLE: Confirm briefly in 1 sentence.",
47808
- inputSchema: {
47809
- roomId: external_exports.number().describe("The room ID"),
47810
- id: external_exports.number().describe("The station subscription ID to start")
47811
- }
47812
- },
47813
- async ({ roomId, id }) => {
47814
- if (!isCloudMode()) return CLOUD_ONLY;
47815
- await bootstrapRoomToken(roomId);
47816
- const cloudRoomId = getRoomCloudId(roomId);
47817
- await startCloudStation(cloudRoomId, id);
47818
- return { content: [{ type: "text", text: `Station ${id} start requested.` }] };
47819
- }
47820
- );
47821
- server.registerTool(
47822
- "quoroom_station_stop",
47823
- {
47824
- title: "Stop Station",
47825
- description: "Stop a running station. RESPONSE STYLE: Confirm briefly in 1 sentence.",
47826
- inputSchema: {
47827
- roomId: external_exports.number().describe("The room ID"),
47828
- id: external_exports.number().describe("The station subscription ID to stop")
47829
- }
47830
- },
47831
- async ({ roomId, id }) => {
47832
- if (!isCloudMode()) return CLOUD_ONLY;
47833
- await bootstrapRoomToken(roomId);
47834
- const cloudRoomId = getRoomCloudId(roomId);
47835
- await stopCloudStation(cloudRoomId, id);
47836
- return { content: [{ type: "text", text: `Station ${id} stop requested.` }] };
47837
- }
47838
- );
47839
- server.registerTool(
47840
- "quoroom_station_delete",
47841
- {
47842
- title: "Delete Station",
47843
- description: "Cancel a station subscription and destroy the Fly.io machine. RESPONSE STYLE: Confirm briefly in 1 sentence.",
47844
- inputSchema: {
47845
- roomId: external_exports.number().describe("The room ID"),
47846
- id: external_exports.number().describe("The station subscription ID to delete")
47847
- }
47848
- },
47849
- async ({ roomId, id }) => {
47850
- if (!isCloudMode()) return CLOUD_ONLY;
47851
- await bootstrapRoomToken(roomId);
47852
- const cloudRoomId = getRoomCloudId(roomId);
47853
- await deleteCloudStation(cloudRoomId, id);
47854
- return {
47855
- content: [{
47856
- type: "text",
47857
- text: `Station ${id} deletion requested (subscription canceled, machine destroyed).`
47858
- }]
47859
- };
47860
- }
47861
- );
47862
- server.registerTool(
47863
- "quoroom_station_cancel",
47864
- {
47865
- title: "Cancel Station",
47866
- description: "Cancel a station subscription at end of billing period. The station keeps running until the period ends, then stops automatically. RESPONSE STYLE: Confirm briefly in 1 sentence.",
47867
- inputSchema: {
47868
- roomId: external_exports.number().describe("The room ID"),
47869
- id: external_exports.number().describe("The station subscription ID to cancel")
47870
- }
47871
- },
47872
- async ({ roomId, id }) => {
47873
- if (!isCloudMode()) return CLOUD_ONLY;
47874
- await bootstrapRoomToken(roomId);
47875
- const cloudRoomId = getRoomCloudId(roomId);
47876
- await cancelCloudStation(cloudRoomId, id);
47877
- return {
47878
- content: [{
47879
- type: "text",
47880
- text: `Station ${id} cancellation requested (will stop at end of billing period).`
47881
- }]
47882
- };
47883
- }
47884
- );
47885
- server.registerTool(
47886
- "quoroom_station_exec",
47887
- {
47888
- title: "Execute on Station",
47889
- description: "Execute a shell command on a station and return stdout/stderr.",
47890
- inputSchema: {
47891
- roomId: external_exports.number().describe("The room ID"),
47892
- id: external_exports.number().describe("The station subscription ID"),
47893
- command: external_exports.string().min(1).describe("Shell command to execute")
47894
- }
47895
- },
47896
- async ({ roomId, id, command }) => {
47897
- if (!isCloudMode()) return CLOUD_ONLY;
47898
- await bootstrapRoomToken(roomId);
47899
- const cloudRoomId = getRoomCloudId(roomId);
47900
- const result = await execOnCloudStation(cloudRoomId, id, command);
47901
- if (!result) {
47902
- return {
47903
- content: [{ type: "text", text: "Failed to execute command on station." }],
47904
- isError: true
47905
- };
47906
- }
47907
- return {
47908
- content: [{
47909
- type: "text",
47910
- text: JSON.stringify({ exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }, null, 2)
47911
- }]
47912
- };
47913
- }
47914
- );
47915
- server.registerTool(
47916
- "quoroom_station_logs",
47917
- {
47918
- title: "Station Logs",
47919
- description: "Get recent logs from a station.",
47920
- inputSchema: {
47921
- roomId: external_exports.number().describe("The room ID"),
47922
- id: external_exports.number().describe("The station subscription ID"),
47923
- lines: external_exports.number().int().positive().max(1e3).optional().describe("Number of log lines (default: all)")
47924
- }
47925
- },
47926
- async ({ roomId, id, lines }) => {
47927
- if (!isCloudMode()) return CLOUD_ONLY;
47928
- await bootstrapRoomToken(roomId);
47929
- const cloudRoomId = getRoomCloudId(roomId);
47930
- const logs = await getCloudStationLogs(cloudRoomId, id, lines);
47931
- if (logs === null) {
47932
- return {
47933
- content: [{ type: "text", text: "Failed to retrieve logs." }],
47934
- isError: true
47935
- };
47936
- }
47937
- return { content: [{ type: "text", text: logs || "(no logs)" }] };
47938
- }
47939
- );
47940
- server.registerTool(
47941
- "quoroom_station_status",
47942
- {
47943
- title: "Station Status",
47944
- description: "Get live status for a station from the cloud.",
47945
- inputSchema: {
47946
- roomId: external_exports.number().describe("The room ID"),
47947
- id: external_exports.number().describe("The station subscription ID")
47948
- }
47949
- },
47950
- async ({ roomId, id }) => {
47951
- if (!isCloudMode()) return CLOUD_ONLY;
47952
- await bootstrapRoomToken(roomId);
47953
- const cloudRoomId = getRoomCloudId(roomId);
47954
- const stations = await listCloudStations(cloudRoomId);
47955
- const station = stations.find((s) => s.id === id);
47956
- if (!station) {
47957
- return {
47958
- content: [{ type: "text", text: `Station ${id} not found` }],
47959
- isError: true
47960
- };
47961
- }
47962
- return {
47963
- content: [{
47964
- type: "text",
47965
- text: JSON.stringify({
47966
- id: station.id,
47967
- name: station.stationName,
47968
- tier: station.tier,
47969
- status: station.status,
47970
- monthlyCost: station.monthlyCost,
47971
- currentPeriodEnd: station.currentPeriodEnd,
47972
- createdAt: station.createdAt
47973
- }, null, 2)
47974
- }]
47975
- };
47976
- }
47977
- );
47978
- server.registerTool(
47979
- "quoroom_station_create_crypto",
47980
- {
47981
- title: "Create Station (Crypto)",
47982
- description: "Pay for a new station with USDC or USDT from the room wallet on any supported chain. Sends stablecoin to the Quoroom treasury and provisions the station automatically. Requires the room to have a wallet with sufficient balance. Crypto prices are 1.5x Stripe prices (micro $7.50, small $22.50, medium $60, large $150). Supported chains: base, ethereum, arbitrum, optimism, polygon. Tokens: usdc, usdt. RESPONSE STYLE: Confirm briefly in 1 sentence.",
47983
- inputSchema: {
47984
- roomId: external_exports.number().describe("The room ID"),
47985
- name: external_exports.string().min(1).max(100).describe('Station name (e.g., "web-server", "scraper-01")'),
47986
- tier: external_exports.enum(["micro", "small", "medium", "large"]).describe(
47987
- "Station tier: micro ($7.50/mo crypto), small ($22.50/mo), medium ($60/mo), large ($150/mo)"
47988
- ),
47989
- encryptionKey: external_exports.string().min(1).describe("Wallet encryption key for sending stablecoin"),
47990
- chain: external_exports.enum(["base", "ethereum", "arbitrum", "optimism", "polygon"]).optional().describe("Chain to pay on (default: base)"),
47991
- token: external_exports.enum(["usdc", "usdt"]).optional().describe("Token to pay with (default: usdc)")
47992
- }
47993
- },
47994
- async ({ roomId, name, tier, encryptionKey, chain, token }) => {
47995
- if (!isCloudMode()) return CLOUD_ONLY;
47996
- const selectedChain = chain ?? "base";
47997
- const selectedToken = token ?? "usdc";
47998
- const chainConfig2 = CHAIN_CONFIGS[selectedChain];
47999
- if (!chainConfig2) {
48000
- return { content: [{ type: "text", text: `Unsupported chain: ${selectedChain}` }], isError: true };
48001
- }
48002
- const tokenConfig = chainConfig2.tokens[selectedToken];
48003
- if (!tokenConfig) {
48004
- return { content: [{ type: "text", text: `Token ${selectedToken} not available on ${selectedChain}` }], isError: true };
48005
- }
48006
- await bootstrapRoomToken(roomId);
48007
- const cloudRoomId = getRoomCloudId(roomId);
48008
- const pricing = await getCloudCryptoPrices(cloudRoomId);
48009
- if (!pricing) {
48010
- return { content: [{ type: "text", text: "Crypto payments are not available." }], isError: true };
48011
- }
48012
- const tierInfo = pricing.tiers.find((t) => t.tier === tier);
48013
- if (!tierInfo) {
48014
- return { content: [{ type: "text", text: `Unknown tier: ${tier}` }], isError: true };
48015
- }
48016
- const db2 = getMcpDatabase();
48017
- let txHash;
48018
- let auditSuffix = "";
48019
- try {
48020
- txHash = await sendToken(
48021
- db2,
48022
- roomId,
48023
- pricing.treasuryAddress,
48024
- tierInfo.cryptoPrice.toString(),
48025
- encryptionKey,
48026
- selectedChain,
48027
- tokenConfig.address,
48028
- tokenConfig.decimals
48029
- );
48030
- const audit = recordPaymentAudit(
48031
- db2,
48032
- roomId,
48033
- `Station crypto payment: create "${name}" (${tier}), paid ${tierInfo.cryptoPrice} ${selectedToken.toUpperCase()} on ${selectedChain} to ${pricing.treasuryAddress}, tx: ${txHash}`
48034
- );
48035
- auditSuffix = formatPaymentAuditSuffix(audit);
48036
- } catch (e) {
48037
- return {
48038
- content: [{ type: "text", text: `Token transfer failed: ${e.message}` }],
48039
- isError: true
48040
- };
48041
- }
48042
- const result = await cryptoCheckoutStation(cloudRoomId, tier, name, txHash, selectedChain);
48043
- if (!result.ok) {
48044
- return {
48045
- content: [{
48046
- type: "text",
48047
- text: `Payment sent (tx: ${txHash}) but provisioning failed: ${result.error}. Contact support with this tx hash.${auditSuffix}`
48048
- }],
48049
- isError: true
48050
- };
48051
- }
48052
- return {
48053
- content: [{
48054
- type: "text",
48055
- text: `Station "${name}" (${tier}) provisioned via crypto. Paid ${tierInfo.cryptoPrice} ${selectedToken.toUpperCase()} on ${selectedChain}, tx: ${txHash}, expires: ${result.currentPeriodEnd}${auditSuffix}`
48056
- }]
48057
- };
48058
- }
48059
- );
48060
- server.registerTool(
48061
- "quoroom_station_renew_crypto",
48062
- {
48063
- title: "Renew Station (Crypto)",
48064
- description: "Renew a crypto-paid station subscription by sending USDC or USDT on any supported chain. Only works for stations originally paid with crypto. Extends the station by 30 days. Supported chains: base, ethereum, arbitrum, optimism, polygon. Tokens: usdc, usdt. RESPONSE STYLE: Confirm briefly in 1 sentence.",
48065
- inputSchema: {
48066
- roomId: external_exports.number().describe("The room ID"),
48067
- id: external_exports.number().describe("The station subscription ID to renew"),
48068
- encryptionKey: external_exports.string().min(1).describe("Wallet encryption key for sending stablecoin"),
48069
- chain: external_exports.enum(["base", "ethereum", "arbitrum", "optimism", "polygon"]).optional().describe("Chain to pay on (default: base)"),
48070
- token: external_exports.enum(["usdc", "usdt"]).optional().describe("Token to pay with (default: usdc)")
48071
- }
48072
- },
48073
- async ({ roomId, id, encryptionKey, chain, token }) => {
48074
- if (!isCloudMode()) return CLOUD_ONLY;
48075
- const selectedChain = chain ?? "base";
48076
- const selectedToken = token ?? "usdc";
48077
- const chainConfig2 = CHAIN_CONFIGS[selectedChain];
48078
- if (!chainConfig2) {
48079
- return { content: [{ type: "text", text: `Unsupported chain: ${selectedChain}` }], isError: true };
48080
- }
48081
- const tokenConfig = chainConfig2.tokens[selectedToken];
48082
- if (!tokenConfig) {
48083
- return { content: [{ type: "text", text: `Token ${selectedToken} not available on ${selectedChain}` }], isError: true };
48084
- }
48085
- await bootstrapRoomToken(roomId);
48086
- const cloudRoomId = getRoomCloudId(roomId);
48087
- const stations = await listCloudStations(cloudRoomId);
48088
- const station = stations.find((s) => s.id === id);
48089
- if (!station) {
48090
- return { content: [{ type: "text", text: `Station ${id} not found.` }], isError: true };
48091
- }
48092
- const pricing = await getCloudCryptoPrices(cloudRoomId);
48093
- if (!pricing) {
48094
- return { content: [{ type: "text", text: "Crypto payments are not available." }], isError: true };
48095
- }
48096
- const tierInfo = pricing.tiers.find((t) => t.tier === station.tier);
48097
- if (!tierInfo) {
48098
- return { content: [{ type: "text", text: `Unknown tier: ${station.tier}` }], isError: true };
48099
- }
48100
- const db2 = getMcpDatabase();
48101
- let txHash;
48102
- let auditSuffix = "";
48103
- try {
48104
- txHash = await sendToken(
48105
- db2,
48106
- roomId,
48107
- pricing.treasuryAddress,
48108
- tierInfo.cryptoPrice.toString(),
48109
- encryptionKey,
48110
- selectedChain,
48111
- tokenConfig.address,
48112
- tokenConfig.decimals
48113
- );
48114
- const audit = recordPaymentAudit(
48115
- db2,
48116
- roomId,
48117
- `Station crypto payment: renew #${id} (${station.tier}), paid ${tierInfo.cryptoPrice} ${selectedToken.toUpperCase()} on ${selectedChain} to ${pricing.treasuryAddress}, tx: ${txHash}`
48118
- );
48119
- auditSuffix = formatPaymentAuditSuffix(audit);
48120
- } catch (e) {
48121
- return {
48122
- content: [{ type: "text", text: `Token transfer failed: ${e.message}` }],
48123
- isError: true
48124
- };
48125
- }
48126
- const result = await cryptoRenewStation(cloudRoomId, id, txHash, selectedChain);
48127
- if (!result.ok) {
48128
- return {
48129
- content: [{
48130
- type: "text",
48131
- text: `Payment sent (tx: ${txHash}) but renewal failed: ${result.error}. Contact support with this tx hash.${auditSuffix}`
48132
- }],
48133
- isError: true
48134
- };
48135
- }
48136
- return {
48137
- content: [{
48138
- type: "text",
48139
- text: `Station ${id} renewed. Paid ${tierInfo.cryptoPrice} ${selectedToken.toUpperCase()} on ${selectedChain}, tx: ${txHash}, new expiry: ${result.currentPeriodEnd}${auditSuffix}`
48140
- }]
48141
- };
48142
- }
48143
- );
48144
- }
48145
-
48146
47116
  // src/mcp/tools/identity.ts
48147
47117
  init_db();
48148
47118
 
@@ -48622,7 +47592,7 @@ function registerResourceTools(server) {
48622
47592
  "quoroom_resources_get",
48623
47593
  {
48624
47594
  title: "Get Local Resources",
48625
- description: "Get current local machine resource usage: CPU load and RAM usage. Use this to decide if the room needs to rent a cloud station for extra compute. If CPU load > number of CPUs or RAM used > 85%, consider proposing a station rental to the quorum.",
47595
+ description: "Get current local machine resource usage: CPU load and RAM usage. Use this to decide if the room needs additional swarm runtime capacity. If CPU load > number of CPUs or RAM used > 85%, consider scaling swarm infrastructure.",
48626
47596
  inputSchema: {}
48627
47597
  },
48628
47598
  async () => {
@@ -48643,7 +47613,7 @@ function registerResourceTools(server) {
48643
47613
  }
48644
47614
  const loadRatio = load1 / cpuCount;
48645
47615
  const highLoad = loadRatio > 0.8 || memUsedPct > 85;
48646
- const summary = highLoad ? `HIGH LOAD \u2014 CPU ${Math.round(loadRatio * 100)}% of capacity, RAM ${memUsedPct}% used. Consider proposing a station rental if funds allow.` : `Normal load \u2014 CPU ${Math.round(loadRatio * 100)}% of capacity, RAM ${memUsedPct}% used.`;
47616
+ const summary = highLoad ? `HIGH LOAD \u2014 CPU ${Math.round(loadRatio * 100)}% of capacity, RAM ${memUsedPct}% used. Consider scaling swarm runtime if funds allow.` : `Normal load \u2014 CPU ${Math.round(loadRatio * 100)}% of capacity, RAM ${memUsedPct}% used.`;
48647
47617
  return {
48648
47618
  content: [{
48649
47619
  type: "text",
@@ -48675,7 +47645,7 @@ function registerResourceTools(server) {
48675
47645
  // src/mcp/tools/invite.ts
48676
47646
  init_db();
48677
47647
  init_db_queries();
48678
- async function bootstrapRoomToken2(roomId) {
47648
+ async function bootstrapRoomToken(roomId) {
48679
47649
  const db2 = getMcpDatabase();
48680
47650
  const room = getRoom(db2, roomId);
48681
47651
  if (!room) return;
@@ -48701,7 +47671,7 @@ function registerInviteTools(server) {
48701
47671
  }
48702
47672
  },
48703
47673
  async ({ roomId, maxUses, expiresInDays }) => {
48704
- await bootstrapRoomToken2(roomId);
47674
+ await bootstrapRoomToken(roomId);
48705
47675
  const cloudRoomId = getRoomCloudId(roomId);
48706
47676
  const result = await createCloudInvite(cloudRoomId, { maxUses, expiresInDays });
48707
47677
  if (!result) {
@@ -48730,7 +47700,7 @@ Share this with the keeper or potential collaborators. Rooms created through thi
48730
47700
  }
48731
47701
  },
48732
47702
  async ({ roomId }) => {
48733
- await bootstrapRoomToken2(roomId);
47703
+ await bootstrapRoomToken(roomId);
48734
47704
  const cloudRoomId = getRoomCloudId(roomId);
48735
47705
  const invites = await listCloudInvites(cloudRoomId);
48736
47706
  if (invites.length === 0) {
@@ -48751,13 +47721,13 @@ Share this with the keeper or potential collaborators. Rooms created through thi
48751
47721
  "quoroom_invite_network",
48752
47722
  {
48753
47723
  title: "View Network",
48754
- description: "See rooms in your network \u2014 rooms created through your invite links. Public rooms show full data (name, goal, workers, stations, earnings). Private rooms show only that they exist.",
47724
+ description: "See rooms in your network \u2014 rooms created through your invite links. Public rooms show full data (name, goal, workers, earnings). Private rooms show only that they exist.",
48755
47725
  inputSchema: {
48756
47726
  roomId: external_exports.number().describe("The room ID")
48757
47727
  }
48758
47728
  },
48759
47729
  async ({ roomId }) => {
48760
- await bootstrapRoomToken2(roomId);
47730
+ await bootstrapRoomToken(roomId);
48761
47731
  const cloudRoomId = getRoomCloudId(roomId);
48762
47732
  const rooms = await fetchReferredRooms(cloudRoomId);
48763
47733
  if (rooms.length === 0) {
@@ -49086,7 +48056,7 @@ init_db();
49086
48056
  async function main() {
49087
48057
  const server = new McpServer({
49088
48058
  name: "quoroom",
49089
- version: true ? "0.1.39" : "0.0.0"
48059
+ version: true ? "0.1.41" : "0.0.0"
49090
48060
  });
49091
48061
  registerMemoryTools(server);
49092
48062
  registerSchedulerTools(server);
@@ -49098,7 +48068,6 @@ async function main() {
49098
48068
  registerSelfModTools(server);
49099
48069
  registerSkillTools(server);
49100
48070
  registerWalletTools(server);
49101
- registerStationTools(server);
49102
48071
  registerIdentityTools(server);
49103
48072
  registerInboxTools(server);
49104
48073
  registerCredentialTools(server);