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.
@@ -9381,19 +9381,19 @@ var init_call = __esm({
9381
9381
  function getMachineId() {
9382
9382
  if (cachedMachineId) return cachedMachineId;
9383
9383
  try {
9384
- const raw = (0, import_os2.hostname)() + (0, import_os2.userInfo)().username;
9385
- cachedMachineId = (0, import_crypto4.createHash)("sha256").update(raw).digest("hex").slice(0, 12);
9384
+ const raw = (0, import_os3.hostname)() + (0, import_os3.userInfo)().username;
9385
+ cachedMachineId = (0, import_crypto5.createHash)("sha256").update(raw).digest("hex").slice(0, 12);
9386
9386
  } catch {
9387
9387
  cachedMachineId = "unknown";
9388
9388
  }
9389
9389
  return cachedMachineId;
9390
9390
  }
9391
- var import_crypto4, import_os2, TELEMETRY_TOKEN, cachedMachineId;
9391
+ var import_crypto5, import_os3, TELEMETRY_TOKEN, cachedMachineId;
9392
9392
  var init_telemetry = __esm({
9393
9393
  "src/shared/telemetry.ts"() {
9394
9394
  "use strict";
9395
- import_crypto4 = require("crypto");
9396
- import_os2 = require("os");
9395
+ import_crypto5 = require("crypto");
9396
+ import_os3 = require("os");
9397
9397
  TELEMETRY_TOKEN = process.env.QUOROOM_TELEMETRY_TOKEN ?? "";
9398
9398
  cachedMachineId = null;
9399
9399
  }
@@ -9402,33 +9402,22 @@ var init_telemetry = __esm({
9402
9402
  // src/shared/cloud-sync.ts
9403
9403
  var cloud_sync_exports = {};
9404
9404
  __export(cloud_sync_exports, {
9405
- cancelCloudStation: () => cancelCloudStation,
9406
9405
  createCloudInvite: () => createCloudInvite,
9407
- cryptoCheckoutStation: () => cryptoCheckoutStation,
9408
- cryptoRenewStation: () => cryptoRenewStation,
9409
- deleteCloudStation: () => deleteCloudStation,
9410
9406
  ensureCloudRoomToken: () => ensureCloudRoomToken,
9411
- execOnCloudStation: () => execOnCloudStation,
9412
9407
  fetchCloudRoomMessages: () => fetchCloudRoomMessages,
9413
9408
  fetchPublicRooms: () => fetchPublicRooms,
9414
9409
  fetchReferredRooms: () => fetchReferredRooms,
9415
9410
  fetchRoomFeed: () => fetchRoomFeed,
9416
- getCloudCryptoPrices: () => getCloudCryptoPrices,
9417
9411
  getCloudOnrampUrl: () => getCloudOnrampUrl,
9418
- getCloudStationLogs: () => getCloudStationLogs,
9419
9412
  getRoomCloudId: () => getRoomCloudId,
9420
9413
  getRoomId: () => getRoomId,
9421
9414
  getStoredCloudRoomToken: () => getStoredCloudRoomToken,
9422
9415
  listCloudInvites: () => listCloudInvites,
9423
- listCloudStationPayments: () => listCloudStationPayments,
9424
- listCloudStations: () => listCloudStations,
9425
9416
  pushActivityToCloud: () => pushActivityToCloud,
9426
9417
  registerWithCloud: () => registerWithCloud,
9427
9418
  sendCloudHeartbeat: () => sendCloudHeartbeat,
9428
9419
  sendCloudRoomMessage: () => sendCloudRoomMessage,
9429
- startCloudStation: () => startCloudStation,
9430
9420
  startCloudSync: () => startCloudSync,
9431
- stopCloudStation: () => stopCloudStation,
9432
9421
  stopCloudSync: () => stopCloudSync
9433
9422
  });
9434
9423
  function getCloudApi() {
@@ -9436,16 +9425,16 @@ function getCloudApi() {
9436
9425
  }
9437
9426
  function getCloudTokenFilePath() {
9438
9427
  const explicitDataDir = process.env.QUOROOM_DATA_DIR?.trim();
9439
- if (explicitDataDir) return (0, import_path2.join)(explicitDataDir, TOKEN_FILE_NAME);
9428
+ if (explicitDataDir) return (0, import_path3.join)(explicitDataDir, TOKEN_FILE_NAME);
9440
9429
  const dbPath = process.env.QUOROOM_DB_PATH?.trim();
9441
- if (dbPath) return (0, import_path2.join)((0, import_path2.dirname)(dbPath), TOKEN_FILE_NAME);
9442
- return (0, import_path2.join)((0, import_os3.homedir)(), ".quoroom", TOKEN_FILE_NAME);
9430
+ if (dbPath) return (0, import_path3.join)((0, import_path3.dirname)(dbPath), TOKEN_FILE_NAME);
9431
+ return (0, import_path3.join)((0, import_os4.homedir)(), ".quoroom", TOKEN_FILE_NAME);
9443
9432
  }
9444
9433
  function loadTokenStore() {
9445
9434
  if (cachedTokens) return cachedTokens;
9446
9435
  const filePath = getCloudTokenFilePath();
9447
9436
  try {
9448
- const parsed = JSON.parse((0, import_fs2.readFileSync)(filePath, "utf-8"));
9437
+ const parsed = JSON.parse((0, import_fs3.readFileSync)(filePath, "utf-8"));
9449
9438
  cachedTokens = parsed.rooms ?? {};
9450
9439
  } catch {
9451
9440
  cachedTokens = {};
@@ -9454,9 +9443,9 @@ function loadTokenStore() {
9454
9443
  }
9455
9444
  function saveTokenStore() {
9456
9445
  const filePath = getCloudTokenFilePath();
9457
- (0, import_fs2.mkdirSync)((0, import_path2.dirname)(filePath), { recursive: true });
9446
+ (0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
9458
9447
  const payload = JSON.stringify({ rooms: loadTokenStore() }, null, 2) + "\n";
9459
- (0, import_fs2.writeFileSync)(filePath, payload, { mode: 384 });
9448
+ (0, import_fs3.writeFileSync)(filePath, payload, { mode: 384 });
9460
9449
  }
9461
9450
  function getRoomToken(roomId) {
9462
9451
  return loadTokenStore()[roomId];
@@ -9585,161 +9574,7 @@ function getRoomId() {
9585
9574
  }
9586
9575
  function getRoomCloudId(dbRoomId) {
9587
9576
  const machineId = getMachineId();
9588
- return (0, import_crypto5.createHash)("sha256").update(`${machineId}:${dbRoomId}`).digest("hex").slice(0, 32);
9589
- }
9590
- function getCachedCloudStations(cloudRoomId) {
9591
- const cached2 = cloudStationsCache.get(cloudRoomId);
9592
- if (!cached2) return null;
9593
- if (Date.now() >= cached2.expiresAt) {
9594
- cloudStationsCache.delete(cloudRoomId);
9595
- return null;
9596
- }
9597
- return cached2.value;
9598
- }
9599
- function setCachedCloudStations(cloudRoomId, stations) {
9600
- cloudStationsCache.set(cloudRoomId, {
9601
- value: stations,
9602
- expiresAt: Date.now() + CLOUD_STATIONS_CACHE_MS
9603
- });
9604
- }
9605
- function invalidateCloudStationsCache(cloudRoomId) {
9606
- cloudStationsCache.delete(cloudRoomId);
9607
- }
9608
- async function listCloudStations(cloudRoomId) {
9609
- const cached2 = getCachedCloudStations(cloudRoomId);
9610
- if (cached2) return cached2;
9611
- try {
9612
- const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations`, {
9613
- headers: cloudHeaders(cloudRoomId),
9614
- signal: AbortSignal.timeout(1e4)
9615
- });
9616
- if (!res.ok) return [];
9617
- const data = await res.json();
9618
- const stations = data.stations ?? [];
9619
- setCachedCloudStations(cloudRoomId, stations);
9620
- return stations;
9621
- } catch {
9622
- return [];
9623
- }
9624
- }
9625
- async function listCloudStationPayments(cloudRoomId) {
9626
- try {
9627
- const res = await fetch(`${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/payments`, {
9628
- headers: cloudHeaders(cloudRoomId),
9629
- signal: AbortSignal.timeout(1e4)
9630
- });
9631
- if (!res.ok) return [];
9632
- const data = await res.json();
9633
- return data.payments ?? [];
9634
- } catch {
9635
- return [];
9636
- }
9637
- }
9638
- async function execOnCloudStation(cloudRoomId, subId, command, timeoutMs = 9e4) {
9639
- try {
9640
- const res = await fetch(
9641
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/exec`,
9642
- {
9643
- method: "POST",
9644
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
9645
- body: JSON.stringify({ command }),
9646
- signal: AbortSignal.timeout(timeoutMs)
9647
- }
9648
- );
9649
- if (!res.ok) return null;
9650
- return res.json();
9651
- } catch {
9652
- return null;
9653
- }
9654
- }
9655
- async function getCloudStationLogs(cloudRoomId, subId, lines) {
9656
- try {
9657
- const query = lines ? `?lines=${lines}` : "";
9658
- const res = await fetch(
9659
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/logs${query}`,
9660
- {
9661
- headers: cloudHeaders(cloudRoomId),
9662
- signal: AbortSignal.timeout(15e3)
9663
- }
9664
- );
9665
- if (!res.ok) return null;
9666
- const data = await res.json();
9667
- return data.logs ?? "";
9668
- } catch {
9669
- return null;
9670
- }
9671
- }
9672
- async function startCloudStation(cloudRoomId, subId) {
9673
- try {
9674
- await fetch(
9675
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/start`,
9676
- {
9677
- method: "POST",
9678
- headers: cloudHeaders(cloudRoomId),
9679
- signal: AbortSignal.timeout(3e4)
9680
- }
9681
- );
9682
- } catch {
9683
- } finally {
9684
- invalidateCloudStationsCache(cloudRoomId);
9685
- }
9686
- }
9687
- async function stopCloudStation(cloudRoomId, subId) {
9688
- try {
9689
- await fetch(
9690
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}/stop`,
9691
- {
9692
- method: "POST",
9693
- headers: cloudHeaders(cloudRoomId),
9694
- signal: AbortSignal.timeout(3e4)
9695
- }
9696
- );
9697
- } catch {
9698
- } finally {
9699
- invalidateCloudStationsCache(cloudRoomId);
9700
- }
9701
- }
9702
- async function deleteCloudStation(cloudRoomId, subId) {
9703
- try {
9704
- await fetch(
9705
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/stations/${subId}`,
9706
- {
9707
- method: "DELETE",
9708
- headers: cloudHeaders(cloudRoomId),
9709
- signal: AbortSignal.timeout(3e4)
9710
- }
9711
- );
9712
- } catch {
9713
- } finally {
9714
- invalidateCloudStationsCache(cloudRoomId);
9715
- }
9716
- }
9717
- async function cancelCloudStation(cloudRoomId, subId) {
9718
- try {
9719
- await fetch(
9720
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/cancel/${subId}`,
9721
- {
9722
- method: "POST",
9723
- headers: cloudHeaders(cloudRoomId),
9724
- signal: AbortSignal.timeout(3e4)
9725
- }
9726
- );
9727
- } catch {
9728
- } finally {
9729
- invalidateCloudStationsCache(cloudRoomId);
9730
- }
9731
- }
9732
- async function getCloudCryptoPrices(cloudRoomId) {
9733
- try {
9734
- const res = await fetch(
9735
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/crypto-prices`,
9736
- { signal: AbortSignal.timeout(1e4) }
9737
- );
9738
- if (!res.ok) return null;
9739
- return res.json();
9740
- } catch {
9741
- return null;
9742
- }
9577
+ return (0, import_crypto6.createHash)("sha256").update(`${machineId}:${dbRoomId}`).digest("hex").slice(0, 32);
9743
9578
  }
9744
9579
  async function getCloudOnrampUrl(cloudRoomId, walletAddress, amount) {
9745
9580
  try {
@@ -9755,42 +9590,6 @@ async function getCloudOnrampUrl(cloudRoomId, walletAddress, amount) {
9755
9590
  return null;
9756
9591
  }
9757
9592
  }
9758
- async function cryptoCheckoutStation(cloudRoomId, tier, stationName, txHash, chain = "base") {
9759
- try {
9760
- const res = await fetch(
9761
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/crypto-checkout`,
9762
- {
9763
- method: "POST",
9764
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
9765
- body: JSON.stringify({ tier, stationName, txHash, chain }),
9766
- signal: AbortSignal.timeout(6e4)
9767
- }
9768
- );
9769
- return res.json();
9770
- } catch (err) {
9771
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
9772
- } finally {
9773
- invalidateCloudStationsCache(cloudRoomId);
9774
- }
9775
- }
9776
- async function cryptoRenewStation(cloudRoomId, subscriptionId, txHash, chain = "base") {
9777
- try {
9778
- const res = await fetch(
9779
- `${getCloudApi()}/rooms/${encodeURIComponent(cloudRoomId)}/billing/crypto-renew/${subscriptionId}`,
9780
- {
9781
- method: "POST",
9782
- headers: cloudHeaders(cloudRoomId, { "Content-Type": "application/json" }),
9783
- body: JSON.stringify({ txHash, chain }),
9784
- signal: AbortSignal.timeout(6e4)
9785
- }
9786
- );
9787
- return res.json();
9788
- } catch (err) {
9789
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
9790
- } finally {
9791
- invalidateCloudStationsCache(cloudRoomId);
9792
- }
9793
- }
9794
9593
  async function sendCloudRoomMessage(fromRoomId, toRoomId, subject, body) {
9795
9594
  try {
9796
9595
  const res = await fetch(`${getCloudApi()}/rooms/message`, {
@@ -9890,20 +9689,18 @@ async function fetchReferredRooms(cloudRoomId) {
9890
9689
  return [];
9891
9690
  }
9892
9691
  }
9893
- var import_crypto5, import_os3, import_path2, import_fs2, TOKEN_FILE_NAME, cachedTokens, heartbeatInterval, CLOUD_STATIONS_CACHE_MS, cloudStationsCache, PUBLIC_ROOMS_CACHE_MS, cachedPublicRooms;
9692
+ var import_crypto6, import_os4, import_path3, import_fs3, TOKEN_FILE_NAME, cachedTokens, heartbeatInterval, PUBLIC_ROOMS_CACHE_MS, cachedPublicRooms;
9894
9693
  var init_cloud_sync = __esm({
9895
9694
  "src/shared/cloud-sync.ts"() {
9896
9695
  "use strict";
9897
- import_crypto5 = require("crypto");
9898
- import_os3 = require("os");
9899
- import_path2 = require("path");
9900
- import_fs2 = require("fs");
9696
+ import_crypto6 = require("crypto");
9697
+ import_os4 = require("os");
9698
+ import_path3 = require("path");
9699
+ import_fs3 = require("fs");
9901
9700
  init_telemetry();
9902
9701
  TOKEN_FILE_NAME = "cloud-room-tokens.json";
9903
9702
  cachedTokens = null;
9904
9703
  heartbeatInterval = null;
9905
- CLOUD_STATIONS_CACHE_MS = 1e4;
9906
- cloudStationsCache = /* @__PURE__ */ new Map();
9907
9704
  PUBLIC_ROOMS_CACHE_MS = 3e4;
9908
9705
  cachedPublicRooms = null;
9909
9706
  }
@@ -9914,7 +9711,7 @@ var require_package = __commonJS({
9914
9711
  "package.json"(exports2, module2) {
9915
9712
  module2.exports = {
9916
9713
  name: "quoroom",
9917
- version: "0.1.39",
9714
+ version: "0.1.41",
9918
9715
  description: "Open-source local AI agent framework \u2014 Queen, Workers, Quorum. Experimental research tool.",
9919
9716
  main: "./out/mcp/server.js",
9920
9717
  bin: {
@@ -10931,6 +10728,7 @@ var require_node_cron = __commonJS({
10931
10728
  // src/server/index.ts
10932
10729
  var index_exports = {};
10933
10730
  __export(index_exports, {
10731
+ _createCloudReadyUpdateHandler: () => createCloudReadyUpdateHandler,
10934
10732
  _isLoopbackAddress: () => isLoopbackAddress,
10935
10733
  _resolveStaticDirForStart: () => resolveStaticDirForStart,
10936
10734
  _shellQuote: () => shellQuote,
@@ -10940,7 +10738,7 @@ __export(index_exports, {
10940
10738
  });
10941
10739
  module.exports = __toCommonJS(index_exports);
10942
10740
  var import_node_http2 = __toESM(require("node:http"));
10943
- var import_node_https3 = __toESM(require("node:https"));
10741
+ var import_node_https2 = __toESM(require("node:https"));
10944
10742
  var import_node_url = require("node:url");
10945
10743
  var import_node_fs6 = __toESM(require("node:fs"));
10946
10744
  var import_node_path9 = __toESM(require("node:path"));
@@ -11313,24 +11111,24 @@ var CHAIN_CONFIGS = {
11313
11111
  var SUPPORTED_CHAINS = ["base", "ethereum", "arbitrum", "optimism", "polygon"];
11314
11112
  var SUPPORTED_TOKENS = ["usdc", "usdt"];
11315
11113
  var QUEEN_DEFAULTS_BY_PLAN = {
11316
- none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 30 },
11317
- // 10 min gap, 30 turns
11318
- pro: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 30 },
11319
- // 5 min gap, 30 turns
11320
- max: { queenCycleGapMs: 30 * 1e3, queenMaxTurns: 30 },
11321
- // 30s gap, 30 turns
11322
- api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 }
11323
- // 2 min gap, 30 turns
11114
+ none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 50 },
11115
+ // 10 min gap, 50 turns
11116
+ pro: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 50 },
11117
+ // 5 min gap, 50 turns
11118
+ max: { queenCycleGapMs: 30 * 1e3, queenMaxTurns: 50 },
11119
+ // 30s gap, 50 turns
11120
+ api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 50 }
11121
+ // 2 min gap, 50 turns
11324
11122
  };
11325
11123
  var CHATGPT_DEFAULTS_BY_PLAN = {
11326
- none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 30 },
11327
- // 10 min gap, 30 turns
11328
- plus: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 30 },
11329
- // 5 min gap, 30 turns
11330
- pro: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 },
11331
- // 2 min gap, 30 turns
11332
- api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 }
11333
- // 2 min gap, 30 turns
11124
+ none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 50 },
11125
+ // 10 min gap, 50 turns
11126
+ plus: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 50 },
11127
+ // 5 min gap, 50 turns
11128
+ pro: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 50 },
11129
+ // 2 min gap, 50 turns
11130
+ api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 50 }
11131
+ // 2 min gap, 50 turns
11334
11132
  };
11335
11133
  var WORKER_ROLE_PRESETS = {
11336
11134
  guardian: {
@@ -12200,7 +11998,7 @@ function mapRoomRow(row) {
12200
11998
  maxConcurrentTasks: row.max_concurrent_tasks ?? 3,
12201
11999
  workerModel: row.worker_model ?? "claude",
12202
12000
  queenCycleGapMs: row.queen_cycle_gap_ms ?? 18e5,
12203
- queenMaxTurns: row.queen_max_turns ?? 3,
12001
+ queenMaxTurns: row.queen_max_turns ?? 50,
12204
12002
  queenQuietFrom: row.queen_quiet_from ?? null,
12205
12003
  queenQuietUntil: row.queen_quiet_until ?? null,
12206
12004
  config,
@@ -12832,49 +12630,6 @@ function getWalletTransactionSummary(db2, walletId) {
12832
12630
  ).get(walletId);
12833
12631
  return { received: received.total.toString(), sent: sent.total.toString() };
12834
12632
  }
12835
- function mapStationRow(row) {
12836
- let config = null;
12837
- if (row.config && typeof row.config === "string") {
12838
- try {
12839
- config = JSON.parse(row.config);
12840
- } catch {
12841
- }
12842
- }
12843
- return {
12844
- id: row.id,
12845
- roomId: row.room_id,
12846
- name: row.name,
12847
- provider: row.provider,
12848
- externalId: row.external_id,
12849
- tier: row.tier,
12850
- region: row.region,
12851
- status: row.status,
12852
- monthlyCost: row.monthly_cost,
12853
- config,
12854
- createdAt: row.created_at,
12855
- updatedAt: row.updated_at
12856
- };
12857
- }
12858
- function getStation(db2, id) {
12859
- const row = db2.prepare("SELECT * FROM stations WHERE id = ?").get(id);
12860
- return row ? mapStationRow(row) : null;
12861
- }
12862
- function listStations(db2, roomId, status2) {
12863
- if (roomId && status2) {
12864
- const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status2);
12865
- return rows2.map(mapStationRow);
12866
- }
12867
- if (roomId) {
12868
- const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
12869
- return rows2.map(mapStationRow);
12870
- }
12871
- if (status2) {
12872
- const rows2 = db2.prepare("SELECT * FROM stations WHERE status = ? ORDER BY created_at DESC").all(status2);
12873
- return rows2.map(mapStationRow);
12874
- }
12875
- const rows = db2.prepare("SELECT * FROM stations ORDER BY created_at DESC").all();
12876
- return rows.map(mapStationRow);
12877
- }
12878
12633
  function mapClerkMessageRow(row) {
12879
12634
  return {
12880
12635
  id: row.id,
@@ -13045,16 +12800,13 @@ function ensureClerkWorker(db2) {
13045
12800
  }
13046
12801
  function getRevenueSummary(db2, roomId) {
13047
12802
  const wallet = getWalletByRoom(db2, roomId);
13048
- if (!wallet) return { totalIncome: 0, totalExpenses: 0, netProfit: 0, stationCosts: 0, transactionCount: 0 };
12803
+ if (!wallet) return { totalIncome: 0, totalExpenses: 0, netProfit: 0, transactionCount: 0 };
13049
12804
  const income = db2.prepare(
13050
12805
  "SELECT COALESCE(SUM(CAST(amount AS REAL)), 0) as total FROM wallet_transactions WHERE wallet_id = ? AND type IN ('receive', 'fund')"
13051
12806
  ).get(wallet.id);
13052
12807
  const expenses = db2.prepare(
13053
12808
  "SELECT COALESCE(SUM(CAST(amount AS REAL)), 0) as total FROM wallet_transactions WHERE wallet_id = ? AND type IN ('send', 'purchase')"
13054
12809
  ).get(wallet.id);
13055
- const stationCosts = db2.prepare(
13056
- "SELECT COALESCE(SUM(CAST(amount AS REAL)), 0) as total FROM wallet_transactions WHERE wallet_id = ? AND category = 'station_cost'"
13057
- ).get(wallet.id);
13058
12810
  const count = db2.prepare(
13059
12811
  "SELECT COUNT(*) as cnt FROM wallet_transactions WHERE wallet_id = ?"
13060
12812
  ).get(wallet.id);
@@ -13062,7 +12814,6 @@ function getRevenueSummary(db2, roomId) {
13062
12814
  totalIncome: income.total,
13063
12815
  totalExpenses: expenses.total,
13064
12816
  netProfit: income.total - expenses.total,
13065
- stationCosts: stationCosts.total,
13066
12817
  transactionCount: count.cnt
13067
12818
  };
13068
12819
  }
@@ -22758,9 +22509,9 @@ var eventBus = new EventBus();
22758
22509
 
22759
22510
  // src/shared/agent-executor.ts
22760
22511
  var import_child_process2 = require("child_process");
22761
- var import_fs3 = require("fs");
22762
- var import_path3 = require("path");
22763
- var import_os4 = require("os");
22512
+ var import_fs2 = require("fs");
22513
+ var import_path2 = require("path");
22514
+ var import_os2 = require("os");
22764
22515
 
22765
22516
  // src/shared/claude-code.ts
22766
22517
  var import_child_process = require("child_process");
@@ -23174,7 +22925,6 @@ function executeClaudeCode(prompt, options) {
23174
22925
  }
23175
22926
 
23176
22927
  // src/shared/agent-executor.ts
23177
- init_cloud_sync();
23178
22928
  var DEFAULT_HTTP_TIMEOUT_MS = 6e4;
23179
22929
  function linkAbortSignal(controller, external) {
23180
22930
  if (!external) return null;
@@ -23196,11 +22946,11 @@ function resolveCodexNodeScript() {
23196
22946
  stdio: ["ignore", "pipe", "ignore"]
23197
22947
  }).trim().split("\n")[0].trim();
23198
22948
  if (!cmdPath) return null;
23199
- const content = (0, import_fs3.readFileSync)(cmdPath, "utf-8");
22949
+ const content = (0, import_fs2.readFileSync)(cmdPath, "utf-8");
23200
22950
  const match = content.match(/%dp0%\\(.+?\.js)/);
23201
22951
  if (!match) return null;
23202
- const script = (0, import_path3.join)((0, import_path3.dirname)(cmdPath), match[1]);
23203
- if ((0, import_fs3.existsSync)(script)) {
22952
+ const script = (0, import_path2.join)((0, import_path2.dirname)(cmdPath), match[1]);
22953
+ if ((0, import_fs2.existsSync)(script)) {
23204
22954
  _codexNodeScript = script;
23205
22955
  return script;
23206
22956
  }
@@ -23280,7 +23030,7 @@ async function executeCodex(options) {
23280
23030
  const nodeScript = resolveCodexNodeScript();
23281
23031
  if (nodeScript) {
23282
23032
  proc = (0, import_child_process2.spawn)(process.execPath, [nodeScript, ...args], {
23283
- cwd: (0, import_os4.homedir)(),
23033
+ cwd: (0, import_os2.homedir)(),
23284
23034
  env: process.env,
23285
23035
  stdio: ["ignore", "pipe", "pipe"],
23286
23036
  windowsHide: true
@@ -23288,7 +23038,7 @@ async function executeCodex(options) {
23288
23038
  } else {
23289
23039
  const codexCmd = process.platform === "win32" ? "codex.cmd" : "codex";
23290
23040
  proc = (0, import_child_process2.spawn)(codexCmd, args, {
23291
- cwd: (0, import_os4.homedir)(),
23041
+ cwd: (0, import_os2.homedir)(),
23292
23042
  env: process.env,
23293
23043
  stdio: ["ignore", "pipe", "pipe"],
23294
23044
  windowsHide: true,
@@ -23899,99 +23649,6 @@ Respond ONLY with a JSON object (no markdown, no explanation):
23899
23649
  }
23900
23650
  return null;
23901
23651
  }
23902
- async function executeApiOnStation(cloudRoomId, stationId, options) {
23903
- const startTime = Date.now();
23904
- const apiKey = options.apiKey;
23905
- if (!apiKey) {
23906
- return immediateError("Missing API key for station execution.");
23907
- }
23908
- if (options.abortSignal?.aborted) {
23909
- return {
23910
- output: "Execution aborted",
23911
- exitCode: 130,
23912
- durationMs: Date.now() - startTime,
23913
- sessionId: null,
23914
- timedOut: false
23915
- };
23916
- }
23917
- const isOpenAiCompat = options.model.startsWith("openai:") || options.model.startsWith("gemini:");
23918
- const messages = [];
23919
- if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
23920
- messages.push({ role: "user", content: options.prompt });
23921
- let url;
23922
- let headers;
23923
- let body;
23924
- if (isOpenAiCompat) {
23925
- const config = resolveOpenAiCompatible(options.model, apiKey);
23926
- const modelName = config ? parseModelSuffix(options.model, config.prefix) || config.defaultModel : parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
23927
- url = config?.url ?? "https://api.openai.com/v1/chat/completions";
23928
- headers = { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" };
23929
- body = JSON.stringify({ model: modelName, messages });
23930
- } else {
23931
- const modelName = parseAnthropicModel(options.model);
23932
- url = "https://api.anthropic.com/v1/messages";
23933
- headers = { "x-api-key": apiKey, "anthropic-version": "2023-06-01", "content-type": "application/json" };
23934
- body = JSON.stringify({
23935
- model: modelName,
23936
- max_tokens: 2048,
23937
- system: options.systemPrompt,
23938
- messages: [{ role: "user", content: options.prompt }]
23939
- });
23940
- }
23941
- const headerFlags = Object.entries(headers).map(([k, v]) => `-H '${k}: ${v}'`).join(" ");
23942
- const b64 = Buffer.from(body).toString("base64");
23943
- const command = `echo '${b64}' | base64 -d | curl -s --max-time 300 ${headerFlags} -d @- '${url}'`;
23944
- const result = await new Promise((resolve2) => {
23945
- let settled = false;
23946
- const finish = (value) => {
23947
- if (settled) return;
23948
- settled = true;
23949
- resolve2(value);
23950
- };
23951
- const onAbort = () => finish({ stdout: "", stderr: "Execution aborted", exitCode: 130 });
23952
- if (options.abortSignal) {
23953
- if (options.abortSignal.aborted) {
23954
- onAbort();
23955
- return;
23956
- }
23957
- options.abortSignal.addEventListener("abort", onAbort, { once: true });
23958
- }
23959
- void execOnCloudStation(cloudRoomId, stationId, command, 36e4).then((value) => finish(value)).catch(() => finish(null)).finally(() => {
23960
- if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
23961
- });
23962
- });
23963
- if (!result) {
23964
- return {
23965
- output: "Error: station execution failed (station unreachable)",
23966
- exitCode: 1,
23967
- durationMs: Date.now() - startTime,
23968
- sessionId: null,
23969
- timedOut: false
23970
- };
23971
- }
23972
- if (result.exitCode !== 0) {
23973
- return {
23974
- output: result.stderr || result.stdout || `Station exec failed with exit code ${result.exitCode}`,
23975
- exitCode: result.exitCode,
23976
- durationMs: Date.now() - startTime,
23977
- sessionId: null,
23978
- timedOut: false
23979
- };
23980
- }
23981
- try {
23982
- const parsed = JSON.parse(result.stdout);
23983
- const output = isOpenAiCompat ? extractOpenAiText(parsed) : extractAnthropicText(parsed);
23984
- return { output, exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
23985
- } catch {
23986
- return {
23987
- output: result.stdout || "(no output from API)",
23988
- exitCode: 1,
23989
- durationMs: Date.now() - startTime,
23990
- sessionId: null,
23991
- timedOut: false
23992
- };
23993
- }
23994
- }
23995
23652
 
23996
23653
  // src/shared/quorum.ts
23997
23654
  function announce(db2, options) {
@@ -24367,7 +24024,7 @@ function createCycleLogBuffer(cycleId, writer, onEntry) {
24367
24024
  }
24368
24025
 
24369
24026
  // src/shared/web-tools.ts
24370
- var import_crypto6 = require("crypto");
24027
+ var import_crypto4 = require("crypto");
24371
24028
  var import_node_os2 = require("node:os");
24372
24029
  var import_node_path2 = __toESM(require("node:path"));
24373
24030
  var MAX_CONTENT_CHARS = 12e3;
@@ -24428,7 +24085,7 @@ async function getOrCreateSession(sessionId) {
24428
24085
  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",
24429
24086
  viewport: { width: 1280, height: 720 }
24430
24087
  });
24431
- const id = sessionId ?? (0, import_crypto6.randomUUID)();
24088
+ const id = sessionId ?? (0, import_crypto4.randomUUID)();
24432
24089
  const session = { context, lastUsed: Date.now(), id };
24433
24090
  _sessions.set(id, session);
24434
24091
  startSessionCleanup();
@@ -26082,11 +25739,6 @@ var CLOUD_EVENT_MAP = {
26082
25739
  "run:completed": "task_completed",
26083
25740
  "run:failed": "task_failed",
26084
25741
  "skill:created": "skill_created",
26085
- "station:created": "station_created",
26086
- "station:started": "station_started",
26087
- "station:stopped": "station_stopped",
26088
- "station:canceled": "station_stopped",
26089
- "station:deleted": "station_stopped",
26090
25742
  "self_mod:edited": "self_mod",
26091
25743
  "self_mod:reverted": "self_mod",
26092
25744
  "wallet:sent": "money_sent",
@@ -26141,7 +25793,6 @@ function initCloudSync(db2) {
26141
25793
  if (allRooms.length === 0) return [];
26142
25794
  const allWorkers = listWorkers(db2);
26143
25795
  const tasks = listTasks(db2);
26144
- const allStations = listStations(db2);
26145
25796
  const version5 = getVersion2();
26146
25797
  const workersPerRoom = /* @__PURE__ */ new Map();
26147
25798
  const workerCounts = /* @__PURE__ */ new Map();
@@ -26152,13 +25803,6 @@ function initCloudSync(db2) {
26152
25803
  list.push({ name: worker.name, state: worker.agentState, model: worker.model ?? void 0 });
26153
25804
  workersPerRoom.set(worker.roomId, list);
26154
25805
  }
26155
- const stationsPerRoom = /* @__PURE__ */ new Map();
26156
- for (const station of allStations) {
26157
- if (station.roomId == null) continue;
26158
- const list = stationsPerRoom.get(station.roomId) ?? [];
26159
- list.push({ name: station.name, status: station.status, tier: station.tier });
26160
- stationsPerRoom.set(station.roomId, list);
26161
- }
26162
25806
  const taskCounts = /* @__PURE__ */ new Map();
26163
25807
  for (const task of tasks) {
26164
25808
  if (task.roomId == null) continue;
@@ -26185,7 +25829,6 @@ function initCloudSync(db2) {
26185
25829
  version: version5,
26186
25830
  queenModel: queen?.model ?? null,
26187
25831
  workers: privateWorkers,
26188
- stations: [],
26189
25832
  visibility: "private"
26190
25833
  };
26191
25834
  }
@@ -26206,7 +25849,6 @@ function initCloudSync(db2) {
26206
25849
  version: version5,
26207
25850
  queenModel: queen?.model ?? null,
26208
25851
  workers: workersPerRoom.get(room.id) ?? [],
26209
- stations: stationsPerRoom.get(room.id) ?? [],
26210
25852
  visibility: "public",
26211
25853
  referredByCode: room.referredByCode,
26212
25854
  keeperReferralCode
@@ -26228,7 +25870,6 @@ var import_node_cron = __toESM(require_node_cron());
26228
25870
  // src/shared/task-runner.ts
26229
25871
  var import_path4 = require("path");
26230
25872
  var import_fs4 = require("fs");
26231
- init_cloud_sync();
26232
25873
 
26233
25874
  // src/shared/learned-context.ts
26234
25875
  var DISTILL_AFTER_RUNS = 3;
@@ -26559,78 +26200,6 @@ async function executeTask(taskId, options) {
26559
26200
  } catch (err) {
26560
26201
  console.warn("Non-fatal: worker resolution failed:", err);
26561
26202
  }
26562
- const isStationModel = model?.startsWith("openai:") || model?.startsWith("anthropic:") || model?.startsWith("claude-api:");
26563
- if (isStationModel && task.roomId) {
26564
- runningTasks.add(taskId);
26565
- taskAbortControllers.set(taskId, taskAbort);
26566
- try {
26567
- const cloudRoomId = getRoomCloudId(task.roomId);
26568
- const stations = await listCloudStations(cloudRoomId);
26569
- const activeStations = stations.filter((s) => s.status === "active");
26570
- if (activeStations.length > 0) {
26571
- const run2 = createTaskRun(db2, taskId);
26572
- try {
26573
- const station = activeStations[run2.id % activeStations.length];
26574
- let augmentedPrompt = prependKeeperReferral(task.prompt, db2);
26575
- try {
26576
- if (task.learnedContext) {
26577
- augmentedPrompt = `## Approach (learned from previous runs):
26578
- ${task.learnedContext}
26579
-
26580
- ---
26581
-
26582
- ${augmentedPrompt}`;
26583
- }
26584
- } catch (err) {
26585
- console.warn("Non-fatal: learned context injection failed:", err);
26586
- }
26587
- try {
26588
- const memoryContext = getTaskMemoryContext(db2, taskId);
26589
- if (memoryContext) {
26590
- augmentedPrompt = `${memoryContext}
26591
-
26592
- ---
26593
-
26594
- ${augmentedPrompt}`;
26595
- }
26596
- } catch (err) {
26597
- console.warn("Non-fatal: memory injection failed:", err);
26598
- }
26599
- const timeoutMs = task.timeoutMinutes != null ? task.timeoutMinutes * 60 * 1e3 : void 0;
26600
- const stationModel = model;
26601
- const apiKey = resolveApiKeyForModel(db2, task.roomId, stationModel);
26602
- const agentResult = await executeApiOnStation(cloudRoomId, station.id, {
26603
- model: stationModel,
26604
- prompt: augmentedPrompt,
26605
- systemPrompt,
26606
- timeoutMs,
26607
- apiKey,
26608
- abortSignal: taskAbort.signal
26609
- });
26610
- const result = agentResultToExecutionResult(agentResult);
26611
- if (taskAbort.signal.aborted) {
26612
- const errorMsg = "Execution aborted";
26613
- completeTaskRun(db2, run2.id, result.stdout || errorMsg, void 0, errorMsg);
26614
- onFailed?.(task, errorMsg);
26615
- return { success: false, output: result.stdout || "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
26616
- }
26617
- return finishRun(db2, run2.id, taskId, task, result, resultsDir, onComplete, onFailed);
26618
- } catch (err) {
26619
- const errorMsg = taskAbort.signal.aborted ? "Execution aborted" : err instanceof Error ? err.message : String(err);
26620
- completeTaskRun(db2, run2.id, "", void 0, errorMsg);
26621
- onFailed?.(task, errorMsg);
26622
- return { success: false, output: "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
26623
- }
26624
- }
26625
- } catch (err) {
26626
- const errorMsg = taskAbort.signal.aborted ? "Execution aborted" : err instanceof Error ? err.message : String(err);
26627
- onFailed?.(task, errorMsg);
26628
- return { success: false, output: "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
26629
- } finally {
26630
- runningTasks.delete(taskId);
26631
- taskAbortControllers.delete(taskId);
26632
- }
26633
- }
26634
26203
  await acquireSlot(getMaxConcurrentTasks(db2, task.roomId));
26635
26204
  runningTasks.add(taskId);
26636
26205
  taskAbortControllers.set(taskId, taskAbort);
@@ -26805,16 +26374,6 @@ ${retryPrompt}`;
26805
26374
  releaseSlot();
26806
26375
  }
26807
26376
  }
26808
- function agentResultToExecutionResult(result) {
26809
- return {
26810
- stdout: result.output,
26811
- stderr: "",
26812
- exitCode: result.exitCode,
26813
- durationMs: result.durationMs,
26814
- timedOut: result.timedOut,
26815
- sessionId: result.sessionId
26816
- };
26817
- }
26818
26377
  function finishRun(db2, runId, taskId, task, result, resultsDir, onComplete, onFailed) {
26819
26378
  const output = result.stdout || result.stderr || "(no output)";
26820
26379
  const resultFilePath = saveResult(resultsDir, task.name, output, result);
@@ -27433,10 +26992,6 @@ var TOOL_NAMES = {
27433
26992
  // Rooms
27434
26993
  quoroom_list_rooms: "listing rooms",
27435
26994
  quoroom_room_status: "checking room status",
27436
- // Stations
27437
- quoroom_station_create: "creating a station",
27438
- quoroom_station_list: "listing stations",
27439
- quoroom_station_exec: "running station command",
27440
26995
  // Identity & invites
27441
26996
  quoroom_identity_get: "checking identity",
27442
26997
  quoroom_invite_create: "creating an invite",
@@ -29751,14 +29306,14 @@ async function issueEmailVerification(db2, email) {
29751
29306
  const { windowStartMs, windowCount } = emailSendGate(db2, nowMs);
29752
29307
  const code = import_node_crypto4.default.randomInt(0, 1e6).toString().padStart(6, "0");
29753
29308
  await sendVerificationCodeEmail(db2, email, code);
29754
- const nowIso3 = new Date(nowMs).toISOString();
29309
+ const nowIso4 = new Date(nowMs).toISOString();
29755
29310
  const expiresAt = new Date(nowMs + EMAIL_VERIFY_CODE_TTL_MINUTES * 60 * 1e3).toISOString();
29756
29311
  const codeHash = hashEmailCode(email, code);
29757
29312
  setSetting2(db2, CONTACT_EMAIL_KEY, email);
29758
29313
  clearSetting(db2, CONTACT_EMAIL_VERIFIED_AT_KEY);
29759
29314
  setSetting2(db2, CONTACT_EMAIL_CODE_HASH_KEY, codeHash);
29760
29315
  setSetting2(db2, CONTACT_EMAIL_CODE_EXPIRES_AT_KEY, expiresAt);
29761
- setSetting2(db2, CONTACT_EMAIL_LAST_SENT_AT_KEY, nowIso3);
29316
+ setSetting2(db2, CONTACT_EMAIL_LAST_SENT_AT_KEY, nowIso4);
29762
29317
  setSetting2(db2, CONTACT_EMAIL_RATE_WINDOW_START_KEY, new Date(windowStartMs).toISOString());
29763
29318
  setSetting2(db2, CONTACT_EMAIL_RATE_WINDOW_COUNT_KEY, String(windowCount + 1));
29764
29319
  return {
@@ -30740,13 +30295,6 @@ function getLocalReferredRooms(db2, roomId) {
30740
30295
  if (task.roomId == null) continue;
30741
30296
  taskCountByRoom.set(task.roomId, (taskCountByRoom.get(task.roomId) ?? 0) + 1);
30742
30297
  }
30743
- const stationsByRoom = /* @__PURE__ */ new Map();
30744
- for (const station of listStations(db2)) {
30745
- if (station.roomId == null) continue;
30746
- const rows = stationsByRoom.get(station.roomId) ?? [];
30747
- rows.push({ name: station.name, status: station.status, tier: station.tier });
30748
- stationsByRoom.set(station.roomId, rows);
30749
- }
30750
30298
  return referredRooms.map((candidate) => {
30751
30299
  if (candidate.visibility !== "public") {
30752
30300
  return {
@@ -30756,7 +30304,6 @@ function getLocalReferredRooms(db2, roomId) {
30756
30304
  };
30757
30305
  }
30758
30306
  const workers = workersByRoom.get(candidate.id) ?? [];
30759
- const stations = stationsByRoom.get(candidate.id) ?? [];
30760
30307
  const wallet = getWalletByRoom(db2, candidate.id);
30761
30308
  const earnings = wallet ? getWalletTransactionSummary(db2, wallet.id).received : "0";
30762
30309
  const queen = candidate.queenWorkerId ? getWorker(db2, candidate.queenWorkerId) : null;
@@ -30770,7 +30317,6 @@ function getLocalReferredRooms(db2, roomId) {
30770
30317
  earnings,
30771
30318
  queenModel: queen?.model ?? candidate.workerModel ?? null,
30772
30319
  workers,
30773
- stations,
30774
30320
  online: candidate.status === "active",
30775
30321
  registeredAt: candidate.createdAt
30776
30322
  };
@@ -31903,7 +31449,7 @@ CREATE TABLE IF NOT EXISTS rooms (
31903
31449
  max_concurrent_tasks INTEGER NOT NULL DEFAULT 3,
31904
31450
  worker_model TEXT NOT NULL DEFAULT 'claude',
31905
31451
  queen_cycle_gap_ms INTEGER NOT NULL DEFAULT 1800000,
31906
- queen_max_turns INTEGER NOT NULL DEFAULT 3,
31452
+ queen_max_turns INTEGER NOT NULL DEFAULT 50,
31907
31453
  queen_quiet_from TEXT,
31908
31454
  queen_quiet_until TEXT,
31909
31455
  config TEXT,
@@ -32257,23 +31803,6 @@ CREATE TABLE IF NOT EXISTS room_messages (
32257
31803
  CREATE INDEX IF NOT EXISTS idx_room_messages_room ON room_messages(room_id);
32258
31804
  CREATE INDEX IF NOT EXISTS idx_room_messages_status ON room_messages(status);
32259
31805
 
32260
- -- Stations
32261
- CREATE TABLE IF NOT EXISTS stations (
32262
- id INTEGER PRIMARY KEY AUTOINCREMENT,
32263
- room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
32264
- name TEXT NOT NULL,
32265
- provider TEXT NOT NULL,
32266
- external_id TEXT,
32267
- tier TEXT NOT NULL,
32268
- region TEXT,
32269
- status TEXT NOT NULL DEFAULT 'provisioning',
32270
- monthly_cost REAL NOT NULL DEFAULT 0,
32271
- config TEXT,
32272
- created_at DATETIME DEFAULT (datetime('now','localtime')),
32273
- updated_at DATETIME DEFAULT (datetime('now','localtime'))
32274
- );
32275
- CREATE INDEX IF NOT EXISTS idx_stations_room ON stations(room_id);
32276
-
32277
31806
  -- Worker cycles (agent loop execution tracking)
32278
31807
  CREATE TABLE IF NOT EXISTS worker_cycles (
32279
31808
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -32357,6 +31886,10 @@ function upsertSetting(database, key, value) {
32357
31886
  }
32358
31887
  function runMigrations(database, log = console.log) {
32359
31888
  database.exec(SCHEMA);
31889
+ const legacyQueenTurnsUpdated = database.prepare(`UPDATE rooms SET queen_max_turns = 50 WHERE queen_max_turns = 3`).run().changes;
31890
+ if (legacyQueenTurnsUpdated > 0) {
31891
+ log(`Migrated: updated ${legacyQueenTurnsUpdated} room(s) queen_max_turns from 3 to 50`);
31892
+ }
32360
31893
  if (!database.prepare("SELECT value FROM settings WHERE key = ?").get("keeper_referral_code")) {
32361
31894
  const code = (0, import_crypto10.randomBytes)(6).toString("base64url").slice(0, 10);
32362
31895
  upsertSetting(database, "keeper_referral_code", code);
@@ -32455,6 +31988,7 @@ function runMigrations(database, log = console.log) {
32455
31988
  log(`Migrated: reset ${ollamaRooms.length} room worker_model(s) to 'claude'`);
32456
31989
  }
32457
31990
  database.prepare(`UPDATE rooms SET autonomy_mode = 'semi' WHERE autonomy_mode IS NULL OR autonomy_mode != 'semi'`).run();
31991
+ database.exec(`DROP TABLE IF EXISTS stations`);
32458
31992
  log("Database schema initialized");
32459
31993
  }
32460
31994
 
@@ -32512,9 +32046,6 @@ function closeServerDatabase() {
32512
32046
  }
32513
32047
  }
32514
32048
 
32515
- // src/server/updateChecker.ts
32516
- var import_node_https2 = __toESM(require("node:https"));
32517
-
32518
32049
  // src/server/autoUpdate.ts
32519
32050
  var import_node_fs4 = __toESM(require("node:fs"));
32520
32051
  var import_node_path6 = __toESM(require("node:path"));
@@ -32672,7 +32203,7 @@ function semverGt(a, b) {
32672
32203
  }
32673
32204
  function getCurrentVersion() {
32674
32205
  try {
32675
- return true ? "0.1.39" : null.version;
32206
+ return true ? "0.1.41" : null.version;
32676
32207
  } catch {
32677
32208
  return "0.0.0";
32678
32209
  }
@@ -32727,11 +32258,37 @@ async function checkAndApplyUpdate(bundleUrl, targetVersion) {
32727
32258
  }
32728
32259
 
32729
32260
  // src/server/updateChecker.ts
32730
- var CHECK_INTERVAL = 4 * 60 * 60 * 1e3;
32261
+ var DEFAULT_CHECK_INTERVAL = 4 * 60 * 60 * 1e3;
32731
32262
  var INITIAL_DELAY = 15e3;
32263
+ var REQUEST_TIMEOUT_MS = 1e4;
32264
+ var BACKOFF_BASE_MS = 3e4;
32265
+ var BACKOFF_MAX_MS = 30 * 60 * 1e3;
32266
+ var GITHUB_RELEASES_URL = "https://api.github.com/repos/quoroom-ai/room/releases?per_page=100";
32267
+ var DEFAULT_RELEASE_URL = "https://github.com/quoroom-ai/room/releases";
32732
32268
  var cached = null;
32733
32269
  var initTimer = null;
32734
32270
  var pollInterval = null;
32271
+ var consecutiveFailures = 0;
32272
+ var backoffUntil = 0;
32273
+ var diagnostics = {
32274
+ lastCheckAt: null,
32275
+ lastSuccessAt: null,
32276
+ lastErrorAt: null,
32277
+ lastErrorCode: null,
32278
+ lastErrorMessage: null,
32279
+ updateSource: null,
32280
+ nextCheckAt: null,
32281
+ consecutiveFailures: 0
32282
+ };
32283
+ var UpdateCheckError = class extends Error {
32284
+ code;
32285
+ source;
32286
+ constructor(code, source, message) {
32287
+ super(message);
32288
+ this.code = code;
32289
+ this.source = source;
32290
+ }
32291
+ };
32735
32292
  function isTestTag(tag) {
32736
32293
  return /-test/i.test(tag);
32737
32294
  }
@@ -32765,59 +32322,219 @@ function pickLatestStable(releases) {
32765
32322
  }
32766
32323
  return bestRelease ?? firstStable;
32767
32324
  }
32768
- function fetchJson(url) {
32769
- return new Promise((resolve2, reject) => {
32770
- const req = import_node_https2.default.get(url, { headers: { "User-Agent": "quoroom-update-checker" } }, (res) => {
32771
- const chunks = [];
32772
- res.on("data", (c) => chunks.push(c));
32773
- res.on("end", () => {
32774
- try {
32775
- resolve2(JSON.parse(Buffer.concat(chunks).toString()));
32776
- } catch (e) {
32777
- reject(e);
32778
- }
32779
- });
32780
- });
32781
- req.on("error", reject);
32782
- req.setTimeout(1e4, () => {
32783
- req.destroy();
32784
- reject(new Error("Timeout"));
32785
- });
32786
- });
32325
+ function nowIso(ts = Date.now()) {
32326
+ return new Date(ts).toISOString();
32327
+ }
32328
+ function normalizeVersion(value) {
32329
+ return value.trim().replace(/^v/, "");
32330
+ }
32331
+ function getBackoffMs(failureCount) {
32332
+ if (failureCount <= 1) return 0;
32333
+ const exponent = Math.min(8, failureCount - 2);
32334
+ return Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** exponent);
32787
32335
  }
32788
- async function forceCheck() {
32336
+ function getGithubToken() {
32337
+ const token = (process.env.QUOROOM_UPDATE_GITHUB_TOKEN || "").trim();
32338
+ return token || null;
32339
+ }
32340
+ function getCloudSourceConfig() {
32341
+ const url = (process.env.QUOROOM_UPDATE_SOURCE_URL || "").trim();
32342
+ if (!url) return null;
32343
+ const token = (process.env.QUOROOM_UPDATE_SOURCE_TOKEN || "").trim() || null;
32344
+ return { url, token };
32345
+ }
32346
+ async function fetchJson(url, source, headers = {}, timeoutMs = REQUEST_TIMEOUT_MS) {
32347
+ const timeout = AbortSignal.timeout(timeoutMs);
32348
+ let response;
32789
32349
  try {
32790
- const releases = await fetchJson(
32791
- "https://api.github.com/repos/quoroom-ai/room/releases?per_page=100"
32350
+ response = await fetch(url, {
32351
+ headers: {
32352
+ "User-Agent": "quoroom-update-checker",
32353
+ Accept: "application/json",
32354
+ ...headers
32355
+ },
32356
+ signal: timeout
32357
+ });
32358
+ } catch (error) {
32359
+ if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
32360
+ throw new UpdateCheckError("timeout", source, `Request timed out after ${timeoutMs}ms`);
32361
+ }
32362
+ throw new UpdateCheckError(
32363
+ "network",
32364
+ source,
32365
+ error instanceof Error ? error.message : "Network error while checking updates"
32792
32366
  );
32793
- if (!Array.isArray(releases)) return;
32794
- const latest = pickLatestStable(releases);
32795
- if (!latest?.assets) return;
32796
- const latestVersion = latest.tag_name.replace(/^v/, "");
32797
- const assets = { mac: null, windows: null, linux: null };
32798
- let updateBundle = null;
32799
- for (const a of latest.assets) {
32800
- const { name, browser_download_url: url } = a;
32801
- if (name.endsWith(".pkg")) assets.mac = url;
32802
- else if (name.toLowerCase().includes("setup") && name.endsWith(".exe")) assets.windows = url;
32803
- else if (name.endsWith(".deb")) assets.linux = url;
32804
- else if (name.startsWith("quoroom-update-") && name.endsWith(".tar.gz")) updateBundle = url;
32805
- }
32806
- cached = { latestVersion, releaseUrl: latest.html_url, assets, updateBundle };
32807
- if (updateBundle && latestVersion) {
32808
- void checkAndApplyUpdate(updateBundle, latestVersion).catch(() => {
32809
- });
32367
+ }
32368
+ if (!response.ok) {
32369
+ const rateLimitRemaining = response.headers.get("x-ratelimit-remaining");
32370
+ const retryAfter = response.headers.get("retry-after");
32371
+ const isRateLimited = response.status === 429 || response.status === 403 && (rateLimitRemaining === "0" || Boolean(retryAfter));
32372
+ if (isRateLimited) {
32373
+ const message = retryAfter ? `Rate limited (HTTP ${response.status}, retry-after=${retryAfter}s)` : `Rate limited (HTTP ${response.status})`;
32374
+ throw new UpdateCheckError("rate_limited", source, message);
32810
32375
  }
32376
+ throw new UpdateCheckError("http_status", source, `HTTP ${response.status}`);
32377
+ }
32378
+ const body = await response.text();
32379
+ try {
32380
+ return JSON.parse(body);
32811
32381
  } catch {
32382
+ throw new UpdateCheckError("invalid_json", source, "Response body is not valid JSON");
32812
32383
  }
32813
32384
  }
32814
- function initUpdateChecker() {
32385
+ function formatFailure(error) {
32386
+ if (error instanceof UpdateCheckError) {
32387
+ return { code: error.code, source: error.source, message: error.message };
32388
+ }
32389
+ if (error instanceof Error) {
32390
+ return { code: "unexpected", source: "github", message: error.message };
32391
+ }
32392
+ return { code: "unexpected", source: "github", message: String(error) };
32393
+ }
32394
+ function parseGithubUpdateInfo(raw) {
32395
+ if (!Array.isArray(raw)) {
32396
+ throw new UpdateCheckError("invalid_payload", "github", "GitHub response is not an array");
32397
+ }
32398
+ const latest = pickLatestStable(raw);
32399
+ if (!latest?.assets) {
32400
+ throw new UpdateCheckError("invalid_payload", "github", "No stable release with assets found");
32401
+ }
32402
+ const latestVersion = normalizeVersion(latest.tag_name);
32403
+ const assets = { mac: null, windows: null, linux: null };
32404
+ let updateBundle = null;
32405
+ for (const a of latest.assets) {
32406
+ const { name, browser_download_url: url } = a;
32407
+ if (name.endsWith(".pkg")) assets.mac = url;
32408
+ else if (name.toLowerCase().includes("setup") && name.endsWith(".exe")) assets.windows = url;
32409
+ else if (name.endsWith(".deb")) assets.linux = url;
32410
+ else if (name.startsWith("quoroom-update-") && name.endsWith(".tar.gz")) updateBundle = url;
32411
+ }
32412
+ if (!updateBundle) {
32413
+ console.error(`[update-checker] Missing update bundle asset for v${latestVersion}`);
32414
+ }
32415
+ return {
32416
+ latestVersion,
32417
+ releaseUrl: latest.html_url || DEFAULT_RELEASE_URL,
32418
+ assets,
32419
+ updateBundle
32420
+ };
32421
+ }
32422
+ function parseCloudUpdateInfo(raw) {
32423
+ const payload = raw ?? {};
32424
+ const version5 = typeof payload.version === "string" ? normalizeVersion(payload.version) : "";
32425
+ const updateBundleUrl = typeof payload.updateBundleUrl === "string" ? payload.updateBundleUrl.trim() : "";
32426
+ const releaseUrl = typeof payload.releaseUrl === "string" ? payload.releaseUrl.trim() : DEFAULT_RELEASE_URL;
32427
+ if (!version5) {
32428
+ throw new UpdateCheckError("invalid_payload", "cloud", "Cloud source returned missing version");
32429
+ }
32430
+ if (!updateBundleUrl) {
32431
+ throw new UpdateCheckError("missing_bundle", "cloud", `Cloud source missing update bundle for v${version5}`);
32432
+ }
32433
+ return {
32434
+ latestVersion: version5,
32435
+ releaseUrl,
32436
+ assets: { mac: null, windows: null, linux: null },
32437
+ updateBundle: updateBundleUrl
32438
+ };
32439
+ }
32440
+ async function fetchFromGithub() {
32441
+ const headers = {};
32442
+ const githubToken = getGithubToken();
32443
+ if (githubToken) headers.Authorization = `Bearer ${githubToken}`;
32444
+ const raw = await fetchJson(GITHUB_RELEASES_URL, "github", headers);
32445
+ return parseGithubUpdateInfo(raw);
32446
+ }
32447
+ async function fetchFromCloudSource(url, token) {
32448
+ const headers = {};
32449
+ if (token) {
32450
+ headers.Authorization = `Bearer ${token}`;
32451
+ headers["X-Update-Token"] = token;
32452
+ }
32453
+ const raw = await fetchJson(url, "cloud", headers);
32454
+ return parseCloudUpdateInfo(raw);
32455
+ }
32456
+ async function resolveLatestUpdateInfo() {
32457
+ const cloudSource = getCloudSourceConfig();
32458
+ let cloudError = null;
32459
+ if (cloudSource) {
32460
+ try {
32461
+ const info = await fetchFromCloudSource(cloudSource.url, cloudSource.token);
32462
+ return { info, source: "cloud" };
32463
+ } catch (error) {
32464
+ const failed = formatFailure(error);
32465
+ cloudError = { code: failed.code, message: failed.message };
32466
+ console.error(`[update-checker] Cloud update source failed (${failed.code}): ${failed.message}`);
32467
+ }
32468
+ }
32469
+ try {
32470
+ const info = await fetchFromGithub();
32471
+ return { info, source: "github" };
32472
+ } catch (error) {
32473
+ const failed = formatFailure(error);
32474
+ if (cloudError) {
32475
+ throw new UpdateCheckError(
32476
+ failed.code,
32477
+ failed.source,
32478
+ `Cloud source failed (${cloudError.code}): ${cloudError.message}; GitHub fallback failed (${failed.code}): ${failed.message}`
32479
+ );
32480
+ }
32481
+ throw error;
32482
+ }
32483
+ }
32484
+ async function forceCheck(options = {}) {
32485
+ diagnostics.lastCheckAt = nowIso();
32486
+ if (!options.ignoreBackoff && backoffUntil > Date.now()) {
32487
+ diagnostics.nextCheckAt = nowIso(backoffUntil);
32488
+ return;
32489
+ }
32490
+ try {
32491
+ const { info, source } = await resolveLatestUpdateInfo();
32492
+ cached = info;
32493
+ diagnostics.updateSource = source;
32494
+ diagnostics.lastSuccessAt = nowIso();
32495
+ diagnostics.lastErrorAt = null;
32496
+ diagnostics.lastErrorCode = null;
32497
+ diagnostics.lastErrorMessage = null;
32498
+ diagnostics.nextCheckAt = null;
32499
+ consecutiveFailures = 0;
32500
+ diagnostics.consecutiveFailures = 0;
32501
+ if (info.updateBundle && info.latestVersion) {
32502
+ const beforeReadyVersion = getReadyUpdateVersion();
32503
+ await checkAndApplyUpdate(info.updateBundle, info.latestVersion).catch((error) => {
32504
+ const message = error instanceof Error ? error.message : String(error);
32505
+ console.error(`[update-checker] Auto-apply failed for v${info.latestVersion}: ${message}`);
32506
+ });
32507
+ const afterReadyVersion = getReadyUpdateVersion();
32508
+ if (options.onReadyUpdate && afterReadyVersion && afterReadyVersion !== beforeReadyVersion) {
32509
+ try {
32510
+ options.onReadyUpdate(afterReadyVersion);
32511
+ } catch (error) {
32512
+ const message = error instanceof Error ? error.message : String(error);
32513
+ console.error(`[update-checker] onReadyUpdate callback failed: ${message}`);
32514
+ }
32515
+ }
32516
+ }
32517
+ } catch (error) {
32518
+ const failed = formatFailure(error);
32519
+ consecutiveFailures += 1;
32520
+ diagnostics.consecutiveFailures = consecutiveFailures;
32521
+ diagnostics.lastErrorAt = nowIso();
32522
+ diagnostics.lastErrorCode = failed.code;
32523
+ diagnostics.lastErrorMessage = failed.message;
32524
+ const backoffMs = getBackoffMs(consecutiveFailures);
32525
+ backoffUntil = backoffMs > 0 ? Date.now() + backoffMs : 0;
32526
+ diagnostics.nextCheckAt = backoffUntil > 0 ? nowIso(backoffUntil) : null;
32527
+ console.error(`[update-checker] Update check failed (${failed.code}, source=${failed.source}): ${failed.message}`);
32528
+ }
32529
+ }
32530
+ function initUpdateChecker(options = {}) {
32815
32531
  if (process.env.NODE_ENV === "test") return;
32532
+ const pollEvery = Number.isFinite(options.pollIntervalMs) && (options.pollIntervalMs ?? 0) > 0 ? Number(options.pollIntervalMs) : DEFAULT_CHECK_INTERVAL;
32816
32533
  initTimer = setTimeout(() => {
32817
- void forceCheck();
32534
+ void forceCheck({ onReadyUpdate: options.onReadyUpdate });
32818
32535
  pollInterval = setInterval(() => {
32819
- void forceCheck();
32820
- }, CHECK_INTERVAL);
32536
+ void forceCheck({ onReadyUpdate: options.onReadyUpdate });
32537
+ }, pollEvery);
32821
32538
  }, INITIAL_DELAY);
32822
32539
  }
32823
32540
  function stopUpdateChecker() {
@@ -32833,8 +32550,11 @@ function stopUpdateChecker() {
32833
32550
  function getUpdateInfo() {
32834
32551
  return cached;
32835
32552
  }
32553
+ function getUpdateDiagnostics() {
32554
+ return { ...diagnostics };
32555
+ }
32836
32556
  async function simulateUpdate() {
32837
- if (!cached) await forceCheck();
32557
+ if (!cached) await forceCheck({ ignoreBackoff: true });
32838
32558
  cached = {
32839
32559
  latestVersion: "99.0.0",
32840
32560
  releaseUrl: "https://github.com/quoroom-ai/room/releases",
@@ -32853,7 +32573,7 @@ var cachedVersion = null;
32853
32573
  function getVersion3() {
32854
32574
  if (cachedVersion) return cachedVersion;
32855
32575
  try {
32856
- cachedVersion = true ? "0.1.39" : null.version;
32576
+ cachedVersion = true ? "0.1.41" : null.version;
32857
32577
  } catch {
32858
32578
  cachedVersion = "unknown";
32859
32579
  }
@@ -32948,7 +32668,7 @@ function registerStatusRoutes(router) {
32948
32668
  return { data: { status: getAutoUpdateStatus(), readyVersion: getReadyUpdateVersion() } };
32949
32669
  });
32950
32670
  router.post("/api/status/check-update", async () => {
32951
- await forceCheck();
32671
+ await forceCheck({ ignoreBackoff: true });
32952
32672
  return { data: { updateInfo: getUpdateInfo() } };
32953
32673
  });
32954
32674
  router.get("/api/status", (ctx) => {
@@ -32983,6 +32703,7 @@ function registerStatusRoutes(router) {
32983
32703
  data.updateInfo = getUpdateInfo();
32984
32704
  data.autoUpdate = getAutoUpdateStatus();
32985
32705
  data.readyUpdateVersion = getReadyUpdateVersion();
32706
+ data.updateDiagnostics = getUpdateDiagnostics();
32986
32707
  }
32987
32708
  if (Object.keys(pending).length > 0) {
32988
32709
  data.pending = pending;
@@ -33331,189 +33052,6 @@ function registerCredentialRoutes(router) {
33331
33052
  });
33332
33053
  }
33333
33054
 
33334
- // src/server/routes/stations.ts
33335
- init_cloud_sync();
33336
- init_telemetry();
33337
- function registerStationRoutes(router) {
33338
- router.get("/api/rooms/:roomId/stations", (ctx) => {
33339
- const roomId = Number(ctx.params.roomId);
33340
- return { data: listStations(ctx.db, roomId) };
33341
- });
33342
- router.get("/api/stations/:id", (ctx) => {
33343
- const id = Number(ctx.params.id);
33344
- const station = getStation(ctx.db, id);
33345
- if (!station) return { status: 404, error: "Station not found" };
33346
- return { data: station };
33347
- });
33348
- router.get("/api/rooms/:roomId/cloud-stations", async (ctx) => {
33349
- const roomId = Number(ctx.params.roomId);
33350
- const room = getRoom(ctx.db, roomId);
33351
- if (!room) return { status: 404, error: "Room not found" };
33352
- const cloudRoomId = getRoomCloudId(roomId);
33353
- await ensureCloudRoomToken({
33354
- roomId: cloudRoomId,
33355
- name: room.name,
33356
- goal: room.goal ?? null,
33357
- visibility: room.visibility
33358
- });
33359
- const stations = await listCloudStations(cloudRoomId);
33360
- return { data: stations };
33361
- });
33362
- router.get("/api/rooms/:roomId/cloud-station-payments", async (ctx) => {
33363
- const roomId = Number(ctx.params.roomId);
33364
- const room = getRoom(ctx.db, roomId);
33365
- if (!room) return { status: 404, error: "Room not found" };
33366
- const cloudRoomId = getRoomCloudId(roomId);
33367
- await ensureCloudRoomToken({
33368
- roomId: cloudRoomId,
33369
- name: room.name,
33370
- goal: room.goal ?? null,
33371
- visibility: room.visibility
33372
- });
33373
- const payments = await listCloudStationPayments(cloudRoomId);
33374
- return { data: payments };
33375
- });
33376
- router.post("/api/rooms/:roomId/cloud-stations/:id/start", async (ctx) => {
33377
- const roomId = Number(ctx.params.roomId);
33378
- const stationId = Number(ctx.params.id);
33379
- const room = getRoom(ctx.db, roomId);
33380
- if (!room) return { status: 404, error: "Room not found" };
33381
- const cloudRoomId = getRoomCloudId(roomId);
33382
- await ensureCloudRoomToken({
33383
- roomId: cloudRoomId,
33384
- name: room.name,
33385
- goal: room.goal ?? null,
33386
- visibility: room.visibility
33387
- });
33388
- await startCloudStation(cloudRoomId, stationId);
33389
- eventBus.emit(`room:${roomId}`, "station:started", { roomId, stationId });
33390
- return { data: { ok: true } };
33391
- });
33392
- router.post("/api/rooms/:roomId/cloud-stations/:id/stop", async (ctx) => {
33393
- const roomId = Number(ctx.params.roomId);
33394
- const stationId = Number(ctx.params.id);
33395
- const room = getRoom(ctx.db, roomId);
33396
- if (!room) return { status: 404, error: "Room not found" };
33397
- const cloudRoomId = getRoomCloudId(roomId);
33398
- await ensureCloudRoomToken({
33399
- roomId: cloudRoomId,
33400
- name: room.name,
33401
- goal: room.goal ?? null,
33402
- visibility: room.visibility
33403
- });
33404
- await stopCloudStation(cloudRoomId, stationId);
33405
- eventBus.emit(`room:${roomId}`, "station:stopped", { roomId, stationId });
33406
- return { data: { ok: true } };
33407
- });
33408
- router.post("/api/rooms/:roomId/cloud-stations/:id/cancel", async (ctx) => {
33409
- const roomId = Number(ctx.params.roomId);
33410
- const stationId = Number(ctx.params.id);
33411
- const room = getRoom(ctx.db, roomId);
33412
- if (!room) return { status: 404, error: "Room not found" };
33413
- const cloudRoomId = getRoomCloudId(roomId);
33414
- await ensureCloudRoomToken({
33415
- roomId: cloudRoomId,
33416
- name: room.name,
33417
- goal: room.goal ?? null,
33418
- visibility: room.visibility
33419
- });
33420
- await cancelCloudStation(cloudRoomId, stationId);
33421
- eventBus.emit(`room:${roomId}`, "station:canceled", { roomId, stationId });
33422
- return { data: { ok: true } };
33423
- });
33424
- router.delete("/api/rooms/:roomId/cloud-stations/:id", async (ctx) => {
33425
- const roomId = Number(ctx.params.roomId);
33426
- const stationId = Number(ctx.params.id);
33427
- const room = getRoom(ctx.db, roomId);
33428
- if (!room) return { status: 404, error: "Room not found" };
33429
- const cloudRoomId = getRoomCloudId(roomId);
33430
- await ensureCloudRoomToken({
33431
- roomId: cloudRoomId,
33432
- name: room.name,
33433
- goal: room.goal ?? null,
33434
- visibility: room.visibility
33435
- });
33436
- await deleteCloudStation(cloudRoomId, stationId);
33437
- eventBus.emit(`room:${roomId}`, "station:deleted", { roomId, stationId });
33438
- return { data: { ok: true } };
33439
- });
33440
- router.get("/api/rooms/:roomId/cloud-stations/crypto-prices", async (ctx) => {
33441
- const roomId = Number(ctx.params.roomId);
33442
- const room = getRoom(ctx.db, roomId);
33443
- if (!room) return { status: 404, error: "Room not found" };
33444
- const cloudRoomId = getRoomCloudId(roomId);
33445
- await ensureCloudRoomToken({
33446
- roomId: cloudRoomId,
33447
- name: room.name,
33448
- goal: room.goal ?? null,
33449
- visibility: room.visibility
33450
- });
33451
- const pricing = await getCloudCryptoPrices(cloudRoomId);
33452
- if (!pricing) return { status: 503, error: "Crypto pricing unavailable" };
33453
- return { data: pricing };
33454
- });
33455
- router.post("/api/rooms/:roomId/cloud-stations/crypto-checkout", async (ctx) => {
33456
- const roomId = Number(ctx.params.roomId);
33457
- const room = getRoom(ctx.db, roomId);
33458
- if (!room) return { status: 404, error: "Room not found" };
33459
- const { tier, name, chain, token } = ctx.body;
33460
- if (!tier || !name) {
33461
- return { status: 400, error: "Missing required fields: tier, name" };
33462
- }
33463
- const encryptionKey = getMachineId();
33464
- const selectedChain = chain ?? "base";
33465
- const selectedToken = token ?? "usdc";
33466
- const chainConfig2 = CHAIN_CONFIGS[selectedChain];
33467
- if (!chainConfig2) return { status: 400, error: `Unsupported chain: ${selectedChain}` };
33468
- const tokenConfig = chainConfig2.tokens[selectedToken];
33469
- if (!tokenConfig) return { status: 400, error: `Token ${selectedToken} not available on ${selectedChain}` };
33470
- const cloudRoomId = getRoomCloudId(roomId);
33471
- await ensureCloudRoomToken({
33472
- roomId: cloudRoomId,
33473
- name: room.name,
33474
- goal: room.goal ?? null,
33475
- visibility: room.visibility
33476
- });
33477
- const pricing = await getCloudCryptoPrices(cloudRoomId);
33478
- if (!pricing) return { status: 503, error: "Crypto payments unavailable" };
33479
- const tierInfo = pricing.tiers.find((t) => t.tier === tier);
33480
- if (!tierInfo) return { status: 400, error: `Unknown tier: ${tier}` };
33481
- let txHash;
33482
- try {
33483
- txHash = await sendToken(
33484
- ctx.db,
33485
- roomId,
33486
- pricing.treasuryAddress,
33487
- tierInfo.cryptoPrice.toString(),
33488
- encryptionKey,
33489
- selectedChain,
33490
- tokenConfig.address,
33491
- tokenConfig.decimals
33492
- );
33493
- } catch (e) {
33494
- return { status: 400, error: `Transfer failed: ${e.message}` };
33495
- }
33496
- const result = await cryptoCheckoutStation(cloudRoomId, tier, name, txHash, selectedChain);
33497
- if (!result.ok) {
33498
- return {
33499
- status: 502,
33500
- error: result.error ?? "Provisioning failed",
33501
- data: { txHash }
33502
- };
33503
- }
33504
- eventBus.emit(`room:${roomId}`, "station:created", { roomId, tier, name });
33505
- eventBus.emit(`room:${roomId}`, "wallet:sent", { roomId, amount: tierInfo.cryptoPrice, to: pricing.treasuryAddress });
33506
- return {
33507
- data: {
33508
- ok: true,
33509
- txHash,
33510
- subscriptionId: result.subscriptionId,
33511
- currentPeriodEnd: result.currentPeriodEnd
33512
- }
33513
- };
33514
- });
33515
- }
33516
-
33517
33055
  // src/server/routes/room-messages.ts
33518
33056
  function registerRoomMessageRoutes(router) {
33519
33057
  router.get("/api/rooms/:roomId/messages", (ctx) => {
@@ -33599,7 +33137,7 @@ var activeByProvider = /* @__PURE__ */ new Map();
33599
33137
  var MAX_LINES = Math.max(50, parseInt(process.env.QUOROOM_PROVIDER_AUTH_MAX_LINES || "300", 10) || 300);
33600
33138
  var SESSION_TIMEOUT_MS = Math.max(3e4, parseInt(process.env.QUOROOM_PROVIDER_AUTH_TIMEOUT_MS || "900000", 10) || 9e5);
33601
33139
  var SESSION_TTL_MS = Math.max(6e4, parseInt(process.env.QUOROOM_PROVIDER_AUTH_TTL_MS || "7200000", 10) || 72e5);
33602
- function nowIso() {
33140
+ function nowIso2() {
33603
33141
  return (/* @__PURE__ */ new Date()).toISOString();
33604
33142
  }
33605
33143
  function getProviderCommand(provider) {
@@ -33672,7 +33210,7 @@ function appendLine(session, stream, rawText) {
33672
33210
  id: ++session.lineSeq,
33673
33211
  stream,
33674
33212
  text,
33675
- timestamp: nowIso()
33213
+ timestamp: nowIso2()
33676
33214
  };
33677
33215
  session.lines.push(line);
33678
33216
  if (session.lines.length > MAX_LINES) {
@@ -33720,7 +33258,7 @@ function finalizeSession(session, status2, exitCode) {
33720
33258
  flushBufferedLines(session);
33721
33259
  session.status = status2;
33722
33260
  session.exitCode = exitCode;
33723
- session.endedAt = nowIso();
33261
+ session.endedAt = nowIso2();
33724
33262
  session.updatedAt = session.endedAt;
33725
33263
  if (activeByProvider.get(session.provider) === session.sessionId) {
33726
33264
  activeByProvider.delete(session.provider);
@@ -33812,7 +33350,7 @@ function startProviderAuthSession(provider) {
33812
33350
  shell: process.platform === "win32"
33813
33351
  });
33814
33352
  registerManagedChildProcess(child);
33815
- const startedAt2 = nowIso();
33353
+ const startedAt2 = nowIso2();
33816
33354
  const session = {
33817
33355
  sessionId: (0, import_node_crypto9.randomUUID)(),
33818
33356
  provider,
@@ -33888,7 +33426,7 @@ var activeByProvider2 = /* @__PURE__ */ new Map();
33888
33426
  var MAX_LINES2 = Math.max(50, parseInt(process.env.QUOROOM_PROVIDER_INSTALL_MAX_LINES || "300", 10) || 300);
33889
33427
  var SESSION_TIMEOUT_MS2 = Math.max(3e4, parseInt(process.env.QUOROOM_PROVIDER_INSTALL_TIMEOUT_MS || "900000", 10) || 9e5);
33890
33428
  var SESSION_TTL_MS2 = Math.max(6e4, parseInt(process.env.QUOROOM_PROVIDER_INSTALL_TTL_MS || "7200000", 10) || 72e5);
33891
- function nowIso2() {
33429
+ function nowIso3() {
33892
33430
  return (/* @__PURE__ */ new Date()).toISOString();
33893
33431
  }
33894
33432
  function getNpmCommand(platform = process.platform) {
@@ -33958,7 +33496,7 @@ function appendLine2(session, stream, rawText) {
33958
33496
  id: ++session.lineSeq,
33959
33497
  stream,
33960
33498
  text,
33961
- timestamp: nowIso2()
33499
+ timestamp: nowIso3()
33962
33500
  };
33963
33501
  session.lines.push(line);
33964
33502
  if (session.lines.length > MAX_LINES2) {
@@ -33988,7 +33526,7 @@ function finalizeSession2(session, status2, exitCode) {
33988
33526
  flushBufferedLines2(session);
33989
33527
  session.status = status2;
33990
33528
  session.exitCode = exitCode;
33991
- session.endedAt = nowIso2();
33529
+ session.endedAt = nowIso3();
33992
33530
  session.updatedAt = session.endedAt;
33993
33531
  if (activeByProvider2.get(session.provider) === session.sessionId) {
33994
33532
  activeByProvider2.delete(session.provider);
@@ -34076,7 +33614,7 @@ function startProviderInstallSession(provider) {
34076
33614
  shell: process.platform === "win32"
34077
33615
  });
34078
33616
  registerManagedChildProcess(child);
34079
- const startedAt2 = nowIso2();
33617
+ const startedAt2 = nowIso3();
34080
33618
  const session = {
34081
33619
  sessionId: (0, import_node_crypto10.randomUUID)(),
34082
33620
  provider,
@@ -34331,7 +33869,6 @@ function registerAllRoutes(router) {
34331
33869
  registerStatusRoutes(router);
34332
33870
  registerWalletRoutes(router);
34333
33871
  registerCredentialRoutes(router);
34334
- registerStationRoutes(router);
34335
33872
  registerRoomMessageRoutes(router);
34336
33873
  registerProviderRoutes(router);
34337
33874
  registerContactRoutes(router);
@@ -34610,7 +34147,7 @@ function streamWithRedirects(url, res, corsHeaders, filename, depth = 0) {
34610
34147
  }
34611
34148
  try {
34612
34149
  const parsed = new import_node_url.URL(url);
34613
- const mod2 = parsed.protocol === "https:" ? import_node_https3.default : import_node_http2.default;
34150
+ const mod2 = parsed.protocol === "https:" ? import_node_https2.default : import_node_http2.default;
34614
34151
  mod2.get(url, { headers: { "User-Agent": "quoroom-updater/1.0" } }, (assetRes) => {
34615
34152
  if ((assetRes.statusCode === 301 || assetRes.statusCode === 302 || assetRes.statusCode === 307) && assetRes.headers.location) {
34616
34153
  assetRes.resume();
@@ -34758,6 +34295,17 @@ function scheduleSelfRestart() {
34758
34295
  return false;
34759
34296
  }
34760
34297
  }
34298
+ function createCloudReadyUpdateHandler(triggerShutdown) {
34299
+ let queued = false;
34300
+ return (version5) => {
34301
+ if (queued) return;
34302
+ queued = true;
34303
+ console.error(`[auto-update] Cloud update v${version5} is ready. Restarting to apply.`);
34304
+ setTimeout(() => {
34305
+ triggerShutdown();
34306
+ }, 120);
34307
+ };
34308
+ }
34761
34309
  var MIME_TYPES = {
34762
34310
  ".html": "text/html; charset=utf-8",
34763
34311
  ".js": "application/javascript; charset=utf-8",
@@ -35055,6 +34603,13 @@ function createApiServer(options = {}) {
35055
34603
  }
35056
34604
  const role = principal.role;
35057
34605
  if (pathname === "/api/status/update/download" && req.method === "GET") {
34606
+ if (isCloudDeployment()) {
34607
+ res.writeHead(409, responseHeaders);
34608
+ res.end(JSON.stringify({
34609
+ error: "Installer download is disabled in cloud deployment. Updates are applied automatically."
34610
+ }));
34611
+ return;
34612
+ }
35058
34613
  if (!isAllowedForRole(role, req.method, pathname, db2)) {
35059
34614
  res.writeHead(403, responseHeaders);
35060
34615
  res.end(JSON.stringify({ error: "Forbidden" }));
@@ -35247,7 +34802,18 @@ function startServer(options = {}) {
35247
34802
  });
35248
34803
  }
35249
34804
  initCloudSync(serverDb);
35250
- initUpdateChecker();
34805
+ if (deploymentMode === "cloud") {
34806
+ const onReadyUpdate = createCloudReadyUpdateHandler(() => {
34807
+ if (requestProcessShutdown) requestProcessShutdown();
34808
+ else process.exit(0);
34809
+ });
34810
+ initUpdateChecker({
34811
+ pollIntervalMs: 15 * 60 * 1e3,
34812
+ onReadyUpdate
34813
+ });
34814
+ } else {
34815
+ initUpdateChecker();
34816
+ }
35251
34817
  startServerRuntime(serverDb);
35252
34818
  function listen() {
35253
34819
  server.listen(port, bindHost, () => {
@@ -35330,6 +34896,7 @@ function startServer(options = {}) {
35330
34896
  }
35331
34897
  // Annotate the CommonJS export names for ESM import in node:
35332
34898
  0 && (module.exports = {
34899
+ _createCloudReadyUpdateHandler,
35333
34900
  _isLoopbackAddress,
35334
34901
  _resolveStaticDirForStart,
35335
34902
  _shellQuote,