quoroom 0.1.19 → 0.1.21

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.
@@ -6713,12 +6713,12 @@ var init_request = __esm({
6713
6713
  init_base();
6714
6714
  init_utils4();
6715
6715
  HttpRequestError = class extends BaseError {
6716
- constructor({ body, cause, details, headers, status, url }) {
6716
+ constructor({ body, cause, details, headers, status: status2, url }) {
6717
6717
  super("HTTP request failed.", {
6718
6718
  cause,
6719
6719
  details,
6720
6720
  metaMessages: [
6721
- status && `Status: ${status}`,
6721
+ status2 && `Status: ${status2}`,
6722
6722
  `URL: ${getUrl(url)}`,
6723
6723
  body && `Request body: ${stringify(body)}`
6724
6724
  ].filter(Boolean),
@@ -6750,7 +6750,7 @@ var init_request = __esm({
6750
6750
  });
6751
6751
  this.body = body;
6752
6752
  this.headers = headers;
6753
- this.status = status;
6753
+ this.status = status2;
6754
6754
  this.url = url;
6755
6755
  }
6756
6756
  };
@@ -9914,7 +9914,7 @@ var require_package = __commonJS({
9914
9914
  "package.json"(exports2, module2) {
9915
9915
  module2.exports = {
9916
9916
  name: "quoroom",
9917
- version: "0.1.19",
9917
+ version: "0.1.21",
9918
9918
  description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
9919
9919
  main: "./out/mcp/server.js",
9920
9920
  bin: {
@@ -10799,7 +10799,7 @@ var require_scheduled_task = __commonJS({
10799
10799
  var require_background_scheduled_task = __commonJS({
10800
10800
  "node_modules/node-cron/src/background-scheduled-task/index.js"(exports2, module2) {
10801
10801
  var EventEmitter = require("events");
10802
- var path4 = require("path");
10802
+ var path5 = require("path");
10803
10803
  var { fork } = require("child_process");
10804
10804
  var uuid = (init_esm_node(), __toCommonJS(esm_node_exports));
10805
10805
  var daemonPath = `${__dirname}/daemon.js`;
@@ -10834,7 +10834,7 @@ var require_background_scheduled_task = __commonJS({
10834
10834
  options.scheduled = true;
10835
10835
  this.forkProcess.send({
10836
10836
  type: "register",
10837
- path: path4.resolve(this.taskPath),
10837
+ path: path5.resolve(this.taskPath),
10838
10838
  cron: this.cronExpression,
10839
10839
  options
10840
10840
  });
@@ -10921,35 +10921,35 @@ __export(index_exports, {
10921
10921
  startServer: () => startServer
10922
10922
  });
10923
10923
  module.exports = __toCommonJS(index_exports);
10924
- var import_node_http = __toESM(require("node:http"));
10925
- var import_node_https2 = __toESM(require("node:https"));
10924
+ var import_node_http2 = __toESM(require("node:http"));
10925
+ var import_node_https3 = __toESM(require("node:https"));
10926
10926
  var import_node_url = require("node:url");
10927
- var import_node_fs4 = __toESM(require("node:fs"));
10928
- var import_node_path5 = __toESM(require("node:path"));
10929
- var import_node_os4 = require("node:os");
10927
+ var import_node_fs5 = __toESM(require("node:fs"));
10928
+ var import_node_path6 = __toESM(require("node:path"));
10929
+ var import_node_os5 = require("node:os");
10930
10930
  var import_node_child_process7 = require("node:child_process");
10931
10931
 
10932
10932
  // src/server/router.ts
10933
10933
  var Router = class {
10934
10934
  routes = [];
10935
- get(path4, handler) {
10936
- this.add("GET", path4, handler);
10935
+ get(path5, handler) {
10936
+ this.add("GET", path5, handler);
10937
10937
  }
10938
- post(path4, handler) {
10939
- this.add("POST", path4, handler);
10938
+ post(path5, handler) {
10939
+ this.add("POST", path5, handler);
10940
10940
  }
10941
- patch(path4, handler) {
10942
- this.add("PATCH", path4, handler);
10941
+ patch(path5, handler) {
10942
+ this.add("PATCH", path5, handler);
10943
10943
  }
10944
- put(path4, handler) {
10945
- this.add("PUT", path4, handler);
10944
+ put(path5, handler) {
10945
+ this.add("PUT", path5, handler);
10946
10946
  }
10947
- delete(path4, handler) {
10948
- this.add("DELETE", path4, handler);
10947
+ delete(path5, handler) {
10948
+ this.add("DELETE", path5, handler);
10949
10949
  }
10950
- add(method, path4, handler) {
10950
+ add(method, path5, handler) {
10951
10951
  const paramNames = [];
10952
- const patternStr = path4.replace(/:(\w+)/g, (_, name) => {
10952
+ const patternStr = path5.replace(/:(\w+)/g, (_, name) => {
10953
10953
  paramNames.push(name);
10954
10954
  return "([^/]+)";
10955
10955
  });
@@ -11266,39 +11266,39 @@ var CHAIN_CONFIGS = {
11266
11266
  var SUPPORTED_CHAINS = ["base", "ethereum", "arbitrum", "optimism", "polygon"];
11267
11267
  var SUPPORTED_TOKENS = ["usdc", "usdt"];
11268
11268
  var QUEEN_DEFAULTS_BY_PLAN = {
11269
- none: { queenCycleGapMs: 30 * 60 * 1e3, queenMaxTurns: 3 },
11270
- // 30 min gap, 3 turns (safe default)
11271
- pro: { queenCycleGapMs: 15 * 60 * 1e3, queenMaxTurns: 5 },
11272
- // 15 min gap, 5 turns
11273
- max: { queenCycleGapMs: 1 * 60 * 1e3, queenMaxTurns: 10 },
11274
- // 1 min gap, 10 turns
11275
- api: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 10 }
11276
- // 5 min gap, 10 turns
11269
+ none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 30 },
11270
+ // 10 min gap, 30 turns
11271
+ pro: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 30 },
11272
+ // 5 min gap, 30 turns
11273
+ max: { queenCycleGapMs: 30 * 1e3, queenMaxTurns: 30 },
11274
+ // 30s gap, 30 turns
11275
+ api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 }
11276
+ // 2 min gap, 30 turns
11277
11277
  };
11278
11278
  var CHATGPT_DEFAULTS_BY_PLAN = {
11279
- none: { queenCycleGapMs: 30 * 60 * 1e3, queenMaxTurns: 3 },
11280
- // 30 min gap, 3 turns (safe default)
11281
- plus: { queenCycleGapMs: 15 * 60 * 1e3, queenMaxTurns: 5 },
11282
- // 15 min gap, 5 turns
11283
- pro: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 10 },
11284
- // 5 min gap, 10 turns
11285
- api: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 10 }
11286
- // 5 min gap, 10 turns
11279
+ none: { queenCycleGapMs: 10 * 60 * 1e3, queenMaxTurns: 30 },
11280
+ // 10 min gap, 30 turns
11281
+ plus: { queenCycleGapMs: 5 * 60 * 1e3, queenMaxTurns: 30 },
11282
+ // 5 min gap, 30 turns
11283
+ pro: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 },
11284
+ // 2 min gap, 30 turns
11285
+ api: { queenCycleGapMs: 2 * 60 * 1e3, queenMaxTurns: 30 }
11286
+ // 2 min gap, 30 turns
11287
11287
  };
11288
11288
  var WORKER_ROLE_PRESETS = {
11289
11289
  guardian: {
11290
- cycleGapMs: 6e4,
11291
- maxTurns: 5,
11290
+ cycleGapMs: 3e4,
11291
+ maxTurns: 15,
11292
11292
  systemPromptPrefix: "Monitor and observe. Do not spawn workers or make purchases. Focus on detecting anomalies in tasks, stations, and worker activity."
11293
11293
  },
11294
11294
  analyst: {
11295
- cycleGapMs: 3e5,
11296
- maxTurns: 15,
11295
+ cycleGapMs: 12e4,
11296
+ maxTurns: 30,
11297
11297
  systemPromptPrefix: "Perform deep analysis. Work to completion on a task, then pause. Prefer depth over frequency."
11298
11298
  },
11299
11299
  writer: {
11300
- cycleGapMs: 3e5,
11301
- maxTurns: 20,
11300
+ cycleGapMs: 12e4,
11301
+ maxTurns: 30,
11302
11302
  systemPromptPrefix: "Produce high-quality written output. Minimize interruptions between drafting sessions."
11303
11303
  }
11304
11304
  };
@@ -11566,16 +11566,16 @@ function getTaskByWebhookToken(db2, token) {
11566
11566
  const row = db2.prepare("SELECT * FROM tasks WHERE webhook_token = ?").get(token);
11567
11567
  return row ? mapTaskRow(row) : null;
11568
11568
  }
11569
- function listTasks(db2, roomId, status) {
11570
- if (roomId != null && status) {
11571
- const rows2 = db2.prepare("SELECT * FROM tasks WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
11569
+ function listTasks(db2, roomId, status2) {
11570
+ if (roomId != null && status2) {
11571
+ const rows2 = db2.prepare("SELECT * FROM tasks WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status2);
11572
11572
  return rows2.map(mapTaskRow);
11573
11573
  }
11574
11574
  if (roomId != null) {
11575
11575
  const rows2 = db2.prepare("SELECT * FROM tasks WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
11576
11576
  return rows2.map(mapTaskRow);
11577
11577
  }
11578
- const rows = status ? db2.prepare("SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC").all(status) : db2.prepare("SELECT * FROM tasks ORDER BY created_at DESC").all();
11578
+ const rows = status2 ? db2.prepare("SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC").all(status2) : db2.prepare("SELECT * FROM tasks ORDER BY created_at DESC").all();
11579
11579
  return rows.map(mapTaskRow);
11580
11580
  }
11581
11581
  function updateTask(db2, id, updates) {
@@ -11630,24 +11630,24 @@ function pauseTask(db2, id) {
11630
11630
  function resumeTask(db2, id) {
11631
11631
  updateTask(db2, id, { status: "active" });
11632
11632
  }
11633
- function createWatch(db2, path4, description, actionPrompt, roomId) {
11634
- const result = db2.prepare("INSERT INTO watches (path, description, action_prompt, room_id) VALUES (?, ?, ?, ?)").run(path4, description ?? null, actionPrompt ?? null, roomId ?? null);
11633
+ function createWatch(db2, path5, description, actionPrompt, roomId) {
11634
+ const result = db2.prepare("INSERT INTO watches (path, description, action_prompt, room_id) VALUES (?, ?, ?, ?)").run(path5, description ?? null, actionPrompt ?? null, roomId ?? null);
11635
11635
  return getWatch(db2, result.lastInsertRowid);
11636
11636
  }
11637
11637
  function getWatch(db2, id) {
11638
11638
  const row = db2.prepare("SELECT * FROM watches WHERE id = ?").get(id);
11639
11639
  return row ? mapWatchRow(row) : null;
11640
11640
  }
11641
- function listWatches(db2, roomId, status) {
11642
- if (roomId != null && status) {
11643
- const rows2 = db2.prepare("SELECT * FROM watches WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
11641
+ function listWatches(db2, roomId, status2) {
11642
+ if (roomId != null && status2) {
11643
+ const rows2 = db2.prepare("SELECT * FROM watches WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status2);
11644
11644
  return rows2.map(mapWatchRow);
11645
11645
  }
11646
11646
  if (roomId != null) {
11647
11647
  const rows2 = db2.prepare("SELECT * FROM watches WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
11648
11648
  return rows2.map(mapWatchRow);
11649
11649
  }
11650
- const rows = status ? db2.prepare("SELECT * FROM watches WHERE status = ? ORDER BY created_at DESC").all(status) : db2.prepare("SELECT * FROM watches ORDER BY created_at DESC").all();
11650
+ const rows = status2 ? db2.prepare("SELECT * FROM watches WHERE status = ? ORDER BY created_at DESC").all(status2) : db2.prepare("SELECT * FROM watches ORDER BY created_at DESC").all();
11651
11651
  return rows.map(mapWatchRow);
11652
11652
  }
11653
11653
  function deleteWatch(db2, id) {
@@ -11691,12 +11691,12 @@ function getTaskRun(db2, id) {
11691
11691
  function completeTaskRun(db2, id, result, resultFile, errorMessage) {
11692
11692
  const run = getTaskRun(db2, id);
11693
11693
  if (!run) return;
11694
- const status = errorMessage ? "failed" : "completed";
11694
+ const status2 = errorMessage ? "failed" : "completed";
11695
11695
  const durationMs = Date.now() - new Date(run.startedAt).getTime();
11696
11696
  db2.prepare(
11697
11697
  `UPDATE task_runs SET finished_at = datetime('now','localtime'), status = ?, result = ?,
11698
11698
  result_file = ?, error_message = ?, duration_ms = ? WHERE id = ?`
11699
- ).run(status, result, resultFile ?? null, errorMessage ?? null, durationMs, id);
11699
+ ).run(status2, result, resultFile ?? null, errorMessage ?? null, durationMs, id);
11700
11700
  const task = getTask(db2, run.taskId);
11701
11701
  const newErrorCount = errorMessage ? (task?.errorCount ?? 0) + 1 : 0;
11702
11702
  db2.prepare(
@@ -11920,8 +11920,8 @@ function ensureTaskMemoryEntity(db2, taskId) {
11920
11920
  function storeTaskResultInMemory(db2, taskId, result, success) {
11921
11921
  const entityId = ensureTaskMemoryEntity(db2, taskId);
11922
11922
  const truncated = result.length > MAX_MEMORY_LENGTH ? result.substring(0, MAX_MEMORY_LENGTH) + "\n[...truncated]" : result;
11923
- const status = success ? "SUCCESS" : "FAILED";
11924
- const content = `[${status}] ${truncated}`;
11923
+ const status2 = success ? "SUCCESS" : "FAILED";
11924
+ const content = `[${status2}] ${truncated}`;
11925
11925
  addObservation(db2, entityId, content, "task_runner");
11926
11926
  const countRow = db2.prepare("SELECT COUNT(*) as cnt FROM observations WHERE entity_id = ?").get(entityId);
11927
11927
  if (countRow.cnt > MAX_OBSERVATIONS_PER_ENTITY) {
@@ -12136,9 +12136,9 @@ function getRoomByWebhookToken(db2, token) {
12136
12136
  const row = db2.prepare("SELECT * FROM rooms WHERE webhook_token = ?").get(token);
12137
12137
  return row ? mapRoomRow(row) : null;
12138
12138
  }
12139
- function listRooms(db2, status) {
12140
- if (status) {
12141
- const rows2 = db2.prepare("SELECT * FROM rooms WHERE status = ? ORDER BY created_at DESC").all(status);
12139
+ function listRooms(db2, status2) {
12140
+ if (status2) {
12141
+ const rows2 = db2.prepare("SELECT * FROM rooms WHERE status = ? ORDER BY created_at DESC").all(status2);
12142
12142
  return rows2.map(mapRoomRow);
12143
12143
  }
12144
12144
  const rows = db2.prepare("SELECT * FROM rooms ORDER BY created_at DESC").all();
@@ -12233,16 +12233,16 @@ function getDecision(db2, id) {
12233
12233
  const row = db2.prepare("SELECT * FROM quorum_decisions WHERE id = ?").get(id);
12234
12234
  return row ? mapDecisionRow(row) : null;
12235
12235
  }
12236
- function listDecisions(db2, roomId, status) {
12237
- if (status) {
12238
- const rows2 = db2.prepare("SELECT * FROM quorum_decisions WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
12236
+ function listDecisions(db2, roomId, status2) {
12237
+ if (status2) {
12238
+ const rows2 = db2.prepare("SELECT * FROM quorum_decisions WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status2);
12239
12239
  return rows2.map(mapDecisionRow);
12240
12240
  }
12241
12241
  const rows = db2.prepare("SELECT * FROM quorum_decisions WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
12242
12242
  return rows.map(mapDecisionRow);
12243
12243
  }
12244
- function resolveDecision(db2, id, status, result) {
12245
- db2.prepare("UPDATE quorum_decisions SET status = ?, result = ?, resolved_at = datetime('now','localtime') WHERE id = ?").run(status, result ?? null, id);
12244
+ function resolveDecision(db2, id, status2, result) {
12245
+ db2.prepare("UPDATE quorum_decisions SET status = ?, result = ?, resolved_at = datetime('now','localtime') WHERE id = ?").run(status2, result ?? null, id);
12246
12246
  }
12247
12247
  function setKeeperVote(db2, decisionId, vote2) {
12248
12248
  db2.prepare("UPDATE quorum_decisions SET keeper_vote = ? WHERE id = ?").run(vote2, decisionId);
@@ -12313,9 +12313,9 @@ function getGoal(db2, id) {
12313
12313
  const row = db2.prepare("SELECT * FROM goals WHERE id = ?").get(id);
12314
12314
  return row ? mapGoalRow(row) : null;
12315
12315
  }
12316
- function listGoals(db2, roomId, status) {
12317
- if (status) {
12318
- const rows2 = db2.prepare("SELECT * FROM goals WHERE room_id = ? AND status = ? ORDER BY created_at ASC").all(roomId, status);
12316
+ function listGoals(db2, roomId, status2) {
12317
+ if (status2) {
12318
+ const rows2 = db2.prepare("SELECT * FROM goals WHERE room_id = ? AND status = ? ORDER BY created_at ASC").all(roomId, status2);
12319
12319
  return rows2.map(mapGoalRow);
12320
12320
  }
12321
12321
  const rows = db2.prepare("SELECT * FROM goals WHERE room_id = ? ORDER BY created_at ASC").all(roomId);
@@ -12532,9 +12532,9 @@ function getPendingEscalations(db2, roomId, toAgentId) {
12532
12532
  const rows = db2.prepare("SELECT * FROM escalations WHERE room_id = ? AND status = 'pending' ORDER BY created_at ASC").all(roomId);
12533
12533
  return rows.map(mapEscalationRow);
12534
12534
  }
12535
- function listEscalations(db2, roomId, status) {
12536
- if (status) {
12537
- const rows2 = db2.prepare("SELECT * FROM escalations WHERE room_id = ? AND status = ? ORDER BY created_at ASC").all(roomId, status);
12535
+ function listEscalations(db2, roomId, status2) {
12536
+ if (status2) {
12537
+ const rows2 = db2.prepare("SELECT * FROM escalations WHERE room_id = ? AND status = ? ORDER BY created_at ASC").all(roomId, status2);
12538
12538
  return rows2.map(mapEscalationRow);
12539
12539
  }
12540
12540
  const rows = db2.prepare("SELECT * FROM escalations WHERE room_id = ? ORDER BY created_at ASC").all(roomId);
@@ -12543,6 +12543,12 @@ function listEscalations(db2, roomId, status) {
12543
12543
  function resolveEscalation(db2, id, answer) {
12544
12544
  db2.prepare("UPDATE escalations SET answer = ?, status = 'resolved', resolved_at = datetime('now','localtime') WHERE id = ?").run(answer, id);
12545
12545
  }
12546
+ function getRecentKeeperAnswers(db2, roomId, fromAgentId, limit = 5) {
12547
+ const rows = db2.prepare(
12548
+ `SELECT * FROM escalations WHERE room_id = ? AND from_agent_id = ? AND status = 'resolved' AND to_agent_id IS NULL ORDER BY resolved_at DESC LIMIT ?`
12549
+ ).all(roomId, fromAgentId, limit);
12550
+ return rows.map(mapEscalationRow);
12551
+ }
12546
12552
  function mapCredentialRow(row) {
12547
12553
  return {
12548
12554
  id: row.id,
@@ -12693,17 +12699,17 @@ function getStation(db2, id) {
12693
12699
  const row = db2.prepare("SELECT * FROM stations WHERE id = ?").get(id);
12694
12700
  return row ? mapStationRow(row) : null;
12695
12701
  }
12696
- function listStations(db2, roomId, status) {
12697
- if (roomId && status) {
12698
- const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
12702
+ function listStations(db2, roomId, status2) {
12703
+ if (roomId && status2) {
12704
+ const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status2);
12699
12705
  return rows2.map(mapStationRow);
12700
12706
  }
12701
12707
  if (roomId) {
12702
12708
  const rows2 = db2.prepare("SELECT * FROM stations WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
12703
12709
  return rows2.map(mapStationRow);
12704
12710
  }
12705
- if (status) {
12706
- const rows2 = db2.prepare("SELECT * FROM stations WHERE status = ? ORDER BY created_at DESC").all(status);
12711
+ if (status2) {
12712
+ const rows2 = db2.prepare("SELECT * FROM stations WHERE status = ? ORDER BY created_at DESC").all(status2);
12707
12713
  return rows2.map(mapStationRow);
12708
12714
  }
12709
12715
  const rows = db2.prepare("SELECT * FROM stations ORDER BY created_at DESC").all();
@@ -12782,9 +12788,9 @@ function getRoomMessage(db2, id) {
12782
12788
  const row = db2.prepare("SELECT * FROM room_messages WHERE id = ?").get(id);
12783
12789
  return row ? mapRoomMessageRow(row) : null;
12784
12790
  }
12785
- function listRoomMessages(db2, roomId, status) {
12786
- if (status) {
12787
- const rows2 = db2.prepare("SELECT * FROM room_messages WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status);
12791
+ function listRoomMessages(db2, roomId, status2) {
12792
+ if (status2) {
12793
+ const rows2 = db2.prepare("SELECT * FROM room_messages WHERE room_id = ? AND status = ? ORDER BY created_at DESC").all(roomId, status2);
12788
12794
  return rows2.map(mapRoomMessageRow);
12789
12795
  }
12790
12796
  const rows = db2.prepare("SELECT * FROM room_messages WHERE room_id = ? ORDER BY created_at DESC").all(roomId);
@@ -12837,11 +12843,11 @@ function getWorkerCycle(db2, id) {
12837
12843
  function completeWorkerCycle(db2, cycleId, errorMessage, usage) {
12838
12844
  const cycle = getWorkerCycle(db2, cycleId);
12839
12845
  if (!cycle) return;
12840
- const status = errorMessage ? "failed" : "completed";
12846
+ const status2 = errorMessage ? "failed" : "completed";
12841
12847
  const durationMs = Date.now() - new Date(cycle.startedAt).getTime();
12842
12848
  db2.prepare(
12843
12849
  "UPDATE worker_cycles SET finished_at = datetime('now','localtime'), status = ?, error_message = ?, duration_ms = ?, input_tokens = ?, output_tokens = ? WHERE id = ?"
12844
- ).run(status, errorMessage ?? null, durationMs, usage?.inputTokens ?? null, usage?.outputTokens ?? null, cycleId);
12850
+ ).run(status2, errorMessage ?? null, durationMs, usage?.inputTokens ?? null, usage?.outputTokens ?? null, cycleId);
12845
12851
  }
12846
12852
  function listRoomCycles(db2, roomId, limit = 20) {
12847
12853
  const safeLimit = clampLimit(limit, 20, 200);
@@ -14366,8 +14372,8 @@ init_toHex();
14366
14372
  function createFilterRequestScope(client, { method }) {
14367
14373
  const requestMap = {};
14368
14374
  if (client.transport.type === "fallback")
14369
- client.transport.onResponse?.(({ method: method_, response: id, status, transport }) => {
14370
- if (status === "success" && method === method_)
14375
+ client.transport.onResponse?.(({ method: method_, response: id, status: status2, transport }) => {
14376
+ if (status2 === "success" && method === method_)
14371
14377
  requestMap[id] = transport.request;
14372
14378
  });
14373
14379
  return (id) => requestMap[id] || client.request;
@@ -16423,7 +16429,7 @@ async function getCallsStatus(client, parameters) {
16423
16429
  method: "eth_getTransactionReceipt",
16424
16430
  params: [`0x${hash3}`]
16425
16431
  }, { dedupe: true }) : void 0));
16426
- const status2 = (() => {
16432
+ const status3 = (() => {
16427
16433
  if (receipts2.some((r) => r === null))
16428
16434
  return 100;
16429
16435
  if (receipts2.every((r) => r?.status === "0x1"))
@@ -16436,7 +16442,7 @@ async function getCallsStatus(client, parameters) {
16436
16442
  atomic: false,
16437
16443
  chainId: hexToNumber2(chainId2),
16438
16444
  receipts: receipts2.filter(Boolean),
16439
- status: status2,
16445
+ status: status3,
16440
16446
  version: "2.0.0"
16441
16447
  };
16442
16448
  }
@@ -16446,7 +16452,7 @@ async function getCallsStatus(client, parameters) {
16446
16452
  });
16447
16453
  }
16448
16454
  const { atomic = false, chainId, receipts, version: version5 = "2.0.0", ...response } = await getStatus(parameters.id);
16449
- const [status, statusCode] = (() => {
16455
+ const [status2, statusCode] = (() => {
16450
16456
  const statusCode2 = response.status;
16451
16457
  if (statusCode2 >= 100 && statusCode2 < 200)
16452
16458
  return ["pending", statusCode2];
@@ -16472,7 +16478,7 @@ async function getCallsStatus(client, parameters) {
16472
16478
  status: receiptStatuses[receipt.status]
16473
16479
  })) ?? [],
16474
16480
  statusCode,
16475
- status,
16481
+ status: status2,
16476
16482
  version: version5
16477
16483
  };
16478
16484
  }
@@ -16482,7 +16488,7 @@ async function waitForCallsStatus(client, parameters) {
16482
16488
  const {
16483
16489
  id,
16484
16490
  pollingInterval = client.pollingInterval,
16485
- status = ({ statusCode }) => statusCode === 200 || statusCode >= 300,
16491
+ status: status2 = ({ statusCode }) => statusCode === 200 || statusCode >= 300,
16486
16492
  retryCount = 4,
16487
16493
  retryDelay = ({ count }) => ~~(1 << count) * 200,
16488
16494
  // exponential backoff
@@ -16510,7 +16516,7 @@ async function waitForCallsStatus(client, parameters) {
16510
16516
  retryCount,
16511
16517
  delay: retryDelay
16512
16518
  });
16513
- if (!status(result))
16519
+ if (!status2(result))
16514
16520
  return;
16515
16521
  done(() => emit.resolve(result));
16516
16522
  } catch (error) {
@@ -19769,14 +19775,14 @@ async function simulateBlocks(client, parameters) {
19769
19775
  const data = call2.error?.data ?? call2.returnData;
19770
19776
  const gasUsed = BigInt(call2.gasUsed);
19771
19777
  const logs = call2.logs?.map((log) => formatLog(log));
19772
- const status = call2.status === "0x1" ? "success" : "failure";
19773
- const result2 = abi2 && status === "success" && data !== "0x" ? decodeFunctionResult({
19778
+ const status2 = call2.status === "0x1" ? "success" : "failure";
19779
+ const result2 = abi2 && status2 === "success" && data !== "0x" ? decodeFunctionResult({
19774
19780
  abi: abi2,
19775
19781
  data,
19776
19782
  functionName
19777
19783
  }) : null;
19778
19784
  const error = (() => {
19779
- if (status === "success")
19785
+ if (status2 === "success")
19780
19786
  return void 0;
19781
19787
  let error2;
19782
19788
  if (data === "0x")
@@ -19796,8 +19802,8 @@ async function simulateBlocks(client, parameters) {
19796
19802
  data,
19797
19803
  gasUsed,
19798
19804
  logs,
19799
- status,
19800
- ...status === "success" ? {
19805
+ status: status2,
19806
+ ...status2 === "success" ? {
19801
19807
  result: result2
19802
19808
  } : {
19803
19809
  error
@@ -21528,12 +21534,12 @@ async function sendCallsSync(client, parameters) {
21528
21534
  const { chain = client.chain } = parameters;
21529
21535
  const timeout = parameters.timeout ?? Math.max((chain?.blockTime ?? 0) * 3, 5e3);
21530
21536
  const result = await getAction(client, sendCalls, "sendCalls")(parameters);
21531
- const status = await getAction(client, waitForCallsStatus, "waitForCallsStatus")({
21537
+ const status2 = await getAction(client, waitForCallsStatus, "waitForCallsStatus")({
21532
21538
  ...parameters,
21533
21539
  id: result.id,
21534
21540
  timeout
21535
21541
  });
21536
- return status;
21542
+ return status2;
21537
21543
  }
21538
21544
 
21539
21545
  // node_modules/viem/_esm/actions/wallet/sendTransactionSync.js
@@ -23209,7 +23215,7 @@ Continue working toward the goal.` : options.prompt
23209
23215
  toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
23210
23216
  }
23211
23217
  }
23212
- resultBlocks.push({ type: "tool_result", id: block.id, content: toolResult });
23218
+ resultBlocks.push({ type: "tool_result", tool_use_id: block.id, content: toolResult });
23213
23219
  }
23214
23220
  messages.push({ role: "user", content: resultBlocks });
23215
23221
  if (options.onSessionUpdate) {
@@ -23675,40 +23681,40 @@ function tally(db2, decisionId) {
23675
23681
  abstainCount++;
23676
23682
  }
23677
23683
  const activeWeight = yesWeight + noWeight;
23678
- let status;
23684
+ let status2;
23679
23685
  const threshold = decision.threshold;
23680
23686
  if (activeWeight === 0) {
23681
- status = "rejected";
23687
+ status2 = "rejected";
23682
23688
  } else if (threshold === "unanimous") {
23683
- status = noWeight === 0 && yesWeight > 0 ? "approved" : "rejected";
23689
+ status2 = noWeight === 0 && yesWeight > 0 ? "approved" : "rejected";
23684
23690
  } else if (threshold === "supermajority") {
23685
- status = yesWeight >= activeWeight * 2 / 3 ? "approved" : "rejected";
23691
+ status2 = yesWeight >= activeWeight * 2 / 3 ? "approved" : "rejected";
23686
23692
  } else {
23687
23693
  if (yesWeight > activeWeight / 2) {
23688
- status = "approved";
23694
+ status2 = "approved";
23689
23695
  } else if (noWeight > activeWeight / 2) {
23690
- status = "rejected";
23696
+ status2 = "rejected";
23691
23697
  } else {
23692
23698
  if (tieBreakerMode === "queen" && queenWorkerId !== null) {
23693
23699
  const queenVote = votes.find((v) => v.workerId === queenWorkerId);
23694
- status = queenVote?.vote === "yes" ? "approved" : "rejected";
23700
+ status2 = queenVote?.vote === "yes" ? "approved" : "rejected";
23695
23701
  } else {
23696
- status = "rejected";
23702
+ status2 = "rejected";
23697
23703
  }
23698
23704
  }
23699
23705
  }
23700
23706
  const yesDisplay = useWeighted ? yesWeight.toFixed(2) : String(yesWeight);
23701
23707
  const noDisplay = useWeighted ? noWeight.toFixed(2) : String(noWeight);
23702
23708
  const result = `Yes: ${yesDisplay}, No: ${noDisplay}, Abstain: ${abstainCount}` + (useWeighted ? " (weighted)" : "");
23703
- resolveDecision(db2, decisionId, status, result);
23709
+ resolveDecision(db2, decisionId, status2, result);
23704
23710
  logRoomActivity(
23705
23711
  db2,
23706
23712
  decision.roomId,
23707
23713
  "decision",
23708
- `Decision ${status}: ${decision.proposal} (${result})`
23714
+ `Decision ${status2}: ${decision.proposal} (${result})`
23709
23715
  );
23710
23716
  creditMissedVotes(db2, votes, voters, room);
23711
- return status;
23717
+ return status2;
23712
23718
  }
23713
23719
  function creditMissedVotes(db2, votes, voters, room) {
23714
23720
  if (!room?.config.voterHealth) return;
@@ -24343,7 +24349,7 @@ var QUEEN_TOOL_DEFINITIONS = [
24343
24349
  },
24344
24350
  queenMaxTurns: {
24345
24351
  type: "number",
24346
- description: "Max tool-call turns per queen cycle (1\u201320)"
24352
+ description: "Max tool-call turns per queen cycle (1\u201350)"
24347
24353
  }
24348
24354
  }
24349
24355
  }
@@ -24411,6 +24417,9 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
24411
24417
  case "quoroom_update_progress": {
24412
24418
  const goalId = Number(args.goalId ?? args.goal_id);
24413
24419
  if (!goalId || isNaN(goalId)) return { content: "Error: goalId is required for quoroom_update_progress. Provide the numeric goal ID.", isError: true };
24420
+ const goalCheck = getGoal(db2, goalId);
24421
+ if (!goalCheck) return { content: `Error: goal #${goalId} not found.`, isError: true };
24422
+ if (goalCheck.roomId !== roomId) return { content: `Error: goal #${goalId} belongs to another room. Your room's goals are shown in the Active Goals section \u2014 use those goal IDs.`, isError: true };
24414
24423
  const observation = String(args.observation ?? args.progress ?? args.message ?? args.text ?? "");
24415
24424
  const metricValue = args.metricValue != null ? Number(args.metricValue) : args.metric_value != null ? Number(args.metric_value) : void 0;
24416
24425
  updateGoalProgress(db2, goalId, observation, metricValue, workerId);
@@ -24420,6 +24429,9 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
24420
24429
  }
24421
24430
  case "quoroom_create_subgoal": {
24422
24431
  const goalId = Number(args.goalId);
24432
+ const goalCheck = getGoal(db2, goalId);
24433
+ if (!goalCheck) return { content: `Error: goal #${goalId} not found.`, isError: true };
24434
+ if (goalCheck.roomId !== roomId) return { content: `Error: goal #${goalId} belongs to another room.`, isError: true };
24423
24435
  const raw = args.descriptions;
24424
24436
  const descriptions = Array.isArray(raw) ? raw.map(String) : [String(raw)];
24425
24437
  const subGoals = decomposeGoal(db2, goalId, descriptions);
@@ -24427,11 +24439,17 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
24427
24439
  }
24428
24440
  case "quoroom_complete_goal": {
24429
24441
  const goalId = Number(args.goalId);
24442
+ const goalCheck = getGoal(db2, goalId);
24443
+ if (!goalCheck) return { content: `Error: goal #${goalId} not found.`, isError: true };
24444
+ if (goalCheck.roomId !== roomId) return { content: `Error: goal #${goalId} belongs to another room.`, isError: true };
24430
24445
  completeGoal(db2, goalId);
24431
24446
  return { content: `Goal #${goalId} marked as completed.` };
24432
24447
  }
24433
24448
  case "quoroom_abandon_goal": {
24434
24449
  const goalId = Number(args.goalId);
24450
+ const goalCheck = getGoal(db2, goalId);
24451
+ if (!goalCheck) return { content: `Error: goal #${goalId} not found.`, isError: true };
24452
+ if (goalCheck.roomId !== roomId) return { content: `Error: goal #${goalId} belongs to another room.`, isError: true };
24435
24453
  const reason = String(args.reason ?? "No reason given");
24436
24454
  abandonGoal(db2, goalId, reason);
24437
24455
  return { content: `Goal #${goalId} abandoned: ${reason}` };
@@ -24563,7 +24581,7 @@ async function executeQueenTool(db2, roomId, workerId, toolName, args) {
24563
24581
  case "quoroom_configure_room": {
24564
24582
  const updates = {};
24565
24583
  if (args.queenCycleGapMs != null) updates.queenCycleGapMs = Math.max(1e4, Number(args.queenCycleGapMs));
24566
- if (args.queenMaxTurns != null) updates.queenMaxTurns = Math.max(1, Math.min(20, Number(args.queenMaxTurns)));
24584
+ if (args.queenMaxTurns != null) updates.queenMaxTurns = Math.max(1, Math.min(50, Number(args.queenMaxTurns)));
24567
24585
  if (Object.keys(updates).length > 0) {
24568
24586
  updateRoom(db2, roomId, updates);
24569
24587
  return { content: `Room configured: ${JSON.stringify(updates)}` };
@@ -24847,10 +24865,12 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
24847
24865
  updateAgentState(db2, worker.id, "thinking");
24848
24866
  logBuffer.addSynthetic("system", `Cycle started \u2014 observing room state...`);
24849
24867
  checkExpiredDecisions(db2);
24850
- const status = getRoomStatus(db2, roomId);
24868
+ const status2 = getRoomStatus(db2, roomId);
24851
24869
  const pendingEscalations = getPendingEscalations(db2, roomId, worker.id);
24870
+ const recentKeeperAnswers = getRecentKeeperAnswers(db2, roomId, worker.id, 5);
24852
24871
  const recentActivity = getRoomActivity(db2, roomId, 15);
24853
- const goalUpdates = status.activeGoals.slice(0, 5).map((g) => ({
24872
+ const goalUpdates = status2.activeGoals.slice(0, 5).map((g) => ({
24873
+ id: g.id,
24854
24874
  goal: g.description,
24855
24875
  progress: g.progress,
24856
24876
  status: g.status
@@ -24875,7 +24895,7 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
24875
24895
  ]);
24876
24896
  } catch {
24877
24897
  }
24878
- const skillContent = loadSkillsForAgent(db2, roomId, status.room.goal ?? "");
24898
+ const skillContent = loadSkillsForAgent(db2, roomId, status2.room.goal ?? "");
24879
24899
  const rolePreset = worker.role ? WORKER_ROLE_PRESETS[worker.role] : void 0;
24880
24900
  const systemPrompt = [
24881
24901
  rolePreset?.systemPromptPrefix ? `${rolePreset.systemPromptPrefix}
@@ -24954,14 +24974,14 @@ ${skillContent}` : ""
24954
24974
  - Share link: https://quoroom.ai/share/v2/${encodedKeeperCode}`
24955
24975
  );
24956
24976
  }
24957
- if (status.room.goal) {
24977
+ if (status2.room.goal) {
24958
24978
  contextParts.push(`## Room Objective
24959
- ${status.room.goal}`);
24979
+ ${status2.room.goal}`);
24960
24980
  }
24961
24981
  if (goalUpdates.length > 0) {
24962
24982
  contextParts.push(`## Active Goals
24963
24983
  ${goalUpdates.map(
24964
- (g) => `- [${Math.round(g.progress * 100)}%] ${g.goal} (${g.status})`
24984
+ (g) => `- [#${g.id}] [${Math.round(g.progress * 100)}%] ${g.goal} (${g.status})`
24965
24985
  ).join("\n")}`);
24966
24986
  }
24967
24987
  const memoryEntities = listEntities(db2, roomId).slice(0, 20);
@@ -24991,12 +25011,27 @@ ${recentResolved.map((d) => {
24991
25011
  return `- ${icon} ${d.status}: "${d.proposal.slice(0, 120)}"`;
24992
25012
  }).join("\n")}`);
24993
25013
  }
24994
- if (pendingEscalations.length > 0) {
25014
+ const pendingToKeeper = pendingEscalations.filter((e) => e.fromAgentId === worker.id && !e.toAgentId);
25015
+ const pendingFromOthers = pendingEscalations.filter((e) => e.fromAgentId !== worker.id);
25016
+ if (pendingToKeeper.length > 0) {
25017
+ contextParts.push(`## Pending Questions to Keeper (awaiting keeper reply)
25018
+ ${pendingToKeeper.map(
25019
+ (e) => `- #${e.id}: ${e.question}`
25020
+ ).join("\n")}`);
25021
+ }
25022
+ if (pendingFromOthers.length > 0) {
24995
25023
  contextParts.push(`## Escalations Awaiting Your Response
24996
- ${pendingEscalations.map(
25024
+ ${pendingFromOthers.map(
24997
25025
  (e) => `- #${e.id}: ${e.question}`
24998
25026
  ).join("\n")}`);
24999
25027
  }
25028
+ if (recentKeeperAnswers.length > 0) {
25029
+ contextParts.push(`## Keeper Answers (recent)
25030
+ ${recentKeeperAnswers.map(
25031
+ (e) => `- Q: ${e.question}
25032
+ A: ${e.answer}`
25033
+ ).join("\n")}`);
25034
+ }
25000
25035
  const activitySlice = recentActivity.slice(0, 15);
25001
25036
  if (activitySlice.length > 0) {
25002
25037
  contextParts.push(`## Recent Activity
@@ -25044,9 +25079,9 @@ ${top3.map(
25044
25079
  (a) => a.eventType === "system" && a.summary.includes("rate limited")
25045
25080
  );
25046
25081
  const settingsParts = [
25047
- `- Cycle gap: ${Math.round(status.room.queenCycleGapMs / 1e3)}s`,
25048
- `- Max turns per cycle: ${status.room.queenMaxTurns}`,
25049
- `- Max concurrent tasks: ${status.room.maxConcurrentTasks}`
25082
+ `- Cycle gap: ${Math.round(status2.room.queenCycleGapMs / 1e3)}s`,
25083
+ `- Max turns per cycle: ${status2.room.queenMaxTurns}`,
25084
+ `- Max concurrent tasks: ${status2.room.maxConcurrentTasks}`
25050
25085
  ];
25051
25086
  if (rateLimitEvents.length > 0) {
25052
25087
  settingsParts.push(`- **Rate limits hit recently: ${rateLimitEvents.length}** (in last ${recentActivity.length} events)`);
@@ -25056,19 +25091,24 @@ ${settingsParts.join("\n")}`);
25056
25091
  const selfRegulateHint = rateLimitEvents.length > 0 ? "\n- **Self-regulate**: You are hitting rate limits. Use quoroom_configure_room to increase your cycle gap or reduce max turns to stay within API limits." : "";
25057
25092
  const isClaude = model === "claude" || model.startsWith("claude-");
25058
25093
  const toolCallInstruction = isClaude ? "Always call tools to take action \u2014 do not just describe what you would do." : "IMPORTANT: You MUST call at least one tool in your response. Respond ONLY with a tool call \u2014 do not write explanatory text without a tool call.";
25094
+ const commsTools = isCli ? "quoroom_inbox_send_keeper (message keeper), quoroom_inbox_list (inter-room), quoroom_inbox_send_room, quoroom_inbox_reply" : "quoroom_ask_keeper";
25095
+ const webTools = isCli ? "(use your built-in web search and fetch tools)" : "quoroom_web_search, quoroom_web_fetch, quoroom_browser";
25059
25096
  const toolList = `**Goals:** quoroom_set_goal, quoroom_update_progress, quoroom_create_subgoal, quoroom_complete_goal, quoroom_abandon_goal
25060
25097
  **Governance:** quoroom_propose, quoroom_vote
25061
25098
  **Workers:** quoroom_create_worker, quoroom_update_worker
25062
25099
  **Tasks:** quoroom_schedule
25063
25100
  **Memory:** quoroom_remember, quoroom_recall
25064
- **Web:** quoroom_web_search, quoroom_web_fetch, quoroom_browser
25065
- **Comms:** quoroom_ask_keeper
25101
+ **Web:** ${webTools}
25102
+ **Comms:** ${commsTools}
25066
25103
  **Settings:** quoroom_configure_room${selfRegulateHint}`;
25104
+ const sendKeeperTool = isCli ? "quoroom_inbox_send_keeper" : "quoroom_ask_keeper";
25067
25105
  contextParts.push(`## Instructions
25068
25106
  Based on the current state, decide what to do next and call the appropriate tools. Available tools:
25069
25107
 
25070
25108
  ${toolList}
25071
25109
 
25110
+ Do NOT "stand by" or wait for anyone \u2014 every cycle must make progress. Act autonomously: make decisions and execute. Inform the keeper of progress or important updates using ${sendKeeperTool}, but never block on a response. If the keeper hasn't replied, proceed with your best judgment.
25111
+
25072
25112
  ${toolCallInstruction}`);
25073
25113
  const prompt = contextParts.join("\n\n");
25074
25114
  updateAgentState(db2, worker.id, "acting");
@@ -25434,8 +25474,8 @@ function registerRoomRoutes(router) {
25434
25474
  });
25435
25475
  router.get("/api/rooms/:id/status", (ctx) => {
25436
25476
  try {
25437
- const status = getRoomStatus(ctx.db, Number(ctx.params.id));
25438
- return { data: status };
25477
+ const status2 = getRoomStatus(ctx.db, Number(ctx.params.id));
25478
+ return { data: status2 };
25439
25479
  } catch (e) {
25440
25480
  return { status: 404, error: e.message };
25441
25481
  }
@@ -25789,17 +25829,17 @@ function registerGoalRoutes(router) {
25789
25829
  );
25790
25830
  }
25791
25831
  if (body.status !== void 0) {
25792
- const status = body.status;
25793
- if (!GOAL_STATUS_VALUES.includes(status)) {
25832
+ const status2 = body.status;
25833
+ if (!GOAL_STATUS_VALUES.includes(status2)) {
25794
25834
  return { status: 400, error: "status is invalid" };
25795
25835
  }
25796
- if (status === "completed") {
25836
+ if (status2 === "completed") {
25797
25837
  completeGoal(ctx.db, id);
25798
- } else if (status === "abandoned") {
25838
+ } else if (status2 === "abandoned") {
25799
25839
  const reason = typeof body.reason === "string" && body.reason.trim() ? body.reason : "Manual status change";
25800
25840
  abandonGoal(ctx.db, id, reason);
25801
25841
  } else {
25802
- updateGoal(ctx.db, id, { status });
25842
+ updateGoal(ctx.db, id, { status: status2 });
25803
25843
  }
25804
25844
  }
25805
25845
  const updated = getGoal(ctx.db, id);
@@ -26720,8 +26760,8 @@ async function fetchCloudTelegramVerificationStatus(tokenHash) {
26720
26760
  });
26721
26761
  }
26722
26762
  const payload = await res.json().catch(() => ({}));
26723
- const status = payload.status;
26724
- const normalizedStatus = status === "verified" ? "verified" : status === "expired" ? "expired" : status === "missing" ? "missing" : "pending";
26763
+ const status2 = payload.status;
26764
+ const normalizedStatus = status2 === "verified" ? "verified" : status2 === "expired" ? "expired" : status2 === "missing" ? "missing" : "pending";
26725
26765
  const telegramId = payload.telegram?.id == null ? null : String(payload.telegram.id);
26726
26766
  return {
26727
26767
  status: normalizedStatus,
@@ -27016,13 +27056,13 @@ function registerContactRoutes(router) {
27016
27056
  return { data: { ok: true, status: "expired" } };
27017
27057
  }
27018
27058
  try {
27019
- const status = await fetchCloudTelegramVerificationStatus(tokenHash);
27020
- setSetting2(ctx.db, CONTACT_TELEGRAM_BOT_USERNAME_KEY, status.botUsername);
27021
- if (status.status === "verified" && status.telegramId) {
27022
- setSetting2(ctx.db, CONTACT_TELEGRAM_ID_KEY, status.telegramId);
27023
- setSetting2(ctx.db, CONTACT_TELEGRAM_USERNAME_KEY, status.username ?? "");
27024
- setSetting2(ctx.db, CONTACT_TELEGRAM_FIRST_NAME_KEY, status.firstName ?? "");
27025
- setSetting2(ctx.db, CONTACT_TELEGRAM_VERIFIED_AT_KEY, status.verifiedAt ?? (/* @__PURE__ */ new Date()).toISOString());
27059
+ const status2 = await fetchCloudTelegramVerificationStatus(tokenHash);
27060
+ setSetting2(ctx.db, CONTACT_TELEGRAM_BOT_USERNAME_KEY, status2.botUsername);
27061
+ if (status2.status === "verified" && status2.telegramId) {
27062
+ setSetting2(ctx.db, CONTACT_TELEGRAM_ID_KEY, status2.telegramId);
27063
+ setSetting2(ctx.db, CONTACT_TELEGRAM_USERNAME_KEY, status2.username ?? "");
27064
+ setSetting2(ctx.db, CONTACT_TELEGRAM_FIRST_NAME_KEY, status2.firstName ?? "");
27065
+ setSetting2(ctx.db, CONTACT_TELEGRAM_VERIFIED_AT_KEY, status2.verifiedAt ?? (/* @__PURE__ */ new Date()).toISOString());
27026
27066
  clearSetting(ctx.db, CONTACT_TELEGRAM_PENDING_HASH_KEY);
27027
27067
  clearSetting(ctx.db, CONTACT_TELEGRAM_PENDING_EXPIRES_AT_KEY);
27028
27068
  void syncCloudContactBindingsSafe(ctx.db);
@@ -27031,23 +27071,23 @@ function registerContactRoutes(router) {
27031
27071
  ok: true,
27032
27072
  status: "verified",
27033
27073
  telegram: {
27034
- id: status.telegramId,
27035
- username: status.username,
27036
- firstName: status.firstName,
27037
- verifiedAt: status.verifiedAt
27074
+ id: status2.telegramId,
27075
+ username: status2.username,
27076
+ firstName: status2.firstName,
27077
+ verifiedAt: status2.verifiedAt
27038
27078
  }
27039
27079
  }
27040
27080
  };
27041
27081
  }
27042
- if (status.status === "expired" || status.status === "missing") {
27082
+ if (status2.status === "expired" || status2.status === "missing") {
27043
27083
  clearSetting(ctx.db, CONTACT_TELEGRAM_PENDING_HASH_KEY);
27044
27084
  clearSetting(ctx.db, CONTACT_TELEGRAM_PENDING_EXPIRES_AT_KEY);
27045
27085
  }
27046
27086
  return {
27047
27087
  data: {
27048
27088
  ok: true,
27049
- status: status.status,
27050
- botUsername: status.botUsername
27089
+ status: status2.status,
27090
+ botUsername: status2.botUsername
27051
27091
  }
27052
27092
  };
27053
27093
  } catch (error) {
@@ -27534,17 +27574,17 @@ function parseLimit2(raw, fallback, max) {
27534
27574
  function registerRunRoutes(router) {
27535
27575
  router.get("/api/runs", (ctx) => {
27536
27576
  const limit = parseLimit2(ctx.query.limit, 20, 500);
27537
- const status = ctx.query.status;
27577
+ const status2 = ctx.query.status;
27538
27578
  const includeResult = ctx.query.includeResult === "1";
27539
27579
  const roomId = ctx.query.roomId ? Number(ctx.query.roomId) : void 0;
27540
27580
  let runs;
27541
27581
  if (roomId) {
27542
27582
  runs = listRunsByRoom(ctx.db, roomId, limit);
27543
- if (status) runs = runs.filter((run) => run.status === status);
27544
- } else if (status === "running") {
27583
+ if (status2) runs = runs.filter((run) => run.status === status2);
27584
+ } else if (status2 === "running") {
27545
27585
  runs = getRunningTaskRuns(ctx.db).slice(0, limit);
27546
- } else if (status) {
27547
- runs = listAllRuns(ctx.db, limit).filter((run) => run.status === status);
27586
+ } else if (status2) {
27587
+ runs = listAllRuns(ctx.db, limit).filter((run) => run.status === status2);
27548
27588
  } else {
27549
27589
  runs = listAllRuns(ctx.db, limit);
27550
27590
  }
@@ -27891,8 +27931,8 @@ function registerEscalationRoutes(router) {
27891
27931
  if (toAgentId != null) {
27892
27932
  return { data: getPendingEscalations(ctx.db, roomId, toAgentId) };
27893
27933
  }
27894
- const status = ctx.query.status;
27895
- return { data: listEscalations(ctx.db, roomId, status) };
27934
+ const status2 = ctx.query.status;
27935
+ return { data: listEscalations(ctx.db, roomId, status2) };
27896
27936
  });
27897
27937
  router.post("/api/escalations/:id/resolve", (ctx) => {
27898
27938
  const id = Number(ctx.params.id);
@@ -27905,6 +27945,10 @@ function registerEscalationRoutes(router) {
27905
27945
  resolveEscalation(ctx.db, id, body.answer);
27906
27946
  const updated = getEscalation(ctx.db, id);
27907
27947
  eventBus.emit(`room:${escalation.roomId}`, "escalation:resolved", updated);
27948
+ const room = getRoom(ctx.db, escalation.roomId);
27949
+ if (room?.queenWorkerId) {
27950
+ triggerAgent(ctx.db, escalation.roomId, room.queenWorkerId);
27951
+ }
27908
27952
  return { data: updated };
27909
27953
  });
27910
27954
  }
@@ -28024,7 +28068,7 @@ function registerChatRoutes(router) {
28024
28068
  }
28025
28069
 
28026
28070
  // src/server/routes/status.ts
28027
- var import_node_os3 = __toESM(require("node:os"));
28071
+ var import_node_os4 = __toESM(require("node:os"));
28028
28072
  var import_node_child_process2 = require("node:child_process");
28029
28073
  var import_node_util = require("node:util");
28030
28074
 
@@ -28642,7 +28686,208 @@ function closeServerDatabase() {
28642
28686
  }
28643
28687
 
28644
28688
  // src/server/updateChecker.ts
28689
+ var import_node_https2 = __toESM(require("node:https"));
28690
+
28691
+ // src/server/autoUpdate.ts
28692
+ var import_node_fs3 = __toESM(require("node:fs"));
28693
+ var import_node_path3 = __toESM(require("node:path"));
28694
+ var import_node_os3 = require("node:os");
28645
28695
  var import_node_https = __toESM(require("node:https"));
28696
+ var import_node_http = __toESM(require("node:http"));
28697
+ var import_node_crypto6 = require("node:crypto");
28698
+ var import_promises = require("node:stream/promises");
28699
+ var USER_APP_DIR = import_node_path3.default.join((0, import_node_os3.homedir)(), ".quoroom", "app");
28700
+ var STAGING_DIR = import_node_path3.default.join((0, import_node_os3.homedir)(), ".quoroom", "app-staging");
28701
+ var BOOT_MARKER = import_node_path3.default.join(USER_APP_DIR, ".booting");
28702
+ var CRASH_COUNT_FILE = import_node_path3.default.join(USER_APP_DIR, ".crash_count");
28703
+ var VERSION_FILE = import_node_path3.default.join(USER_APP_DIR, "version.json");
28704
+ var status = { state: "idle" };
28705
+ var downloadInProgress = false;
28706
+ function getAutoUpdateStatus() {
28707
+ if (status.state === "idle" && import_node_fs3.default.existsSync(VERSION_FILE)) {
28708
+ try {
28709
+ const info = JSON.parse(import_node_fs3.default.readFileSync(VERSION_FILE, "utf-8"));
28710
+ return { state: "ready", version: info.version };
28711
+ } catch {
28712
+ }
28713
+ }
28714
+ return status;
28715
+ }
28716
+ function initBootHealthCheck() {
28717
+ if (import_node_fs3.default.existsSync(VERSION_FILE)) {
28718
+ try {
28719
+ const info = JSON.parse(import_node_fs3.default.readFileSync(VERSION_FILE, "utf-8"));
28720
+ const currentVersion = getCurrentVersion();
28721
+ if (info.version && !semverGt(info.version, currentVersion)) {
28722
+ console.error(`[auto-update] Cleaning stale user-space update v${info.version} (bundled is v${currentVersion})`);
28723
+ import_node_fs3.default.rmSync(USER_APP_DIR, { recursive: true, force: true });
28724
+ }
28725
+ } catch {
28726
+ }
28727
+ }
28728
+ if (!import_node_fs3.default.existsSync(USER_APP_DIR)) return;
28729
+ try {
28730
+ import_node_fs3.default.writeFileSync(BOOT_MARKER, JSON.stringify({ pid: process.pid, at: Date.now() }));
28731
+ } catch {
28732
+ }
28733
+ setTimeout(() => {
28734
+ try {
28735
+ import_node_fs3.default.unlinkSync(BOOT_MARKER);
28736
+ } catch {
28737
+ }
28738
+ try {
28739
+ import_node_fs3.default.unlinkSync(CRASH_COUNT_FILE);
28740
+ } catch {
28741
+ }
28742
+ }, 3e4);
28743
+ }
28744
+ function followRedirects(url, maxRedirects = 5) {
28745
+ return new Promise((resolve2, reject) => {
28746
+ if (maxRedirects <= 0) return reject(new Error("Too many redirects"));
28747
+ const parsed = new URL(url);
28748
+ const mod2 = parsed.protocol === "https:" ? import_node_https.default : import_node_http.default;
28749
+ const req = mod2.get(url, { headers: { "User-Agent": "quoroom-auto-updater/1.0" } }, (res) => {
28750
+ if ((res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) && res.headers.location) {
28751
+ res.resume();
28752
+ followRedirects(res.headers.location, maxRedirects - 1).then(resolve2, reject);
28753
+ return;
28754
+ }
28755
+ if (res.statusCode !== 200) {
28756
+ res.resume();
28757
+ reject(new Error(`HTTP ${res.statusCode}`));
28758
+ return;
28759
+ }
28760
+ resolve2(res);
28761
+ });
28762
+ req.on("error", reject);
28763
+ req.setTimeout(6e4, () => {
28764
+ req.destroy();
28765
+ reject(new Error("Download timeout"));
28766
+ });
28767
+ });
28768
+ }
28769
+ function sha256File(filePath) {
28770
+ return new Promise((resolve2, reject) => {
28771
+ const hash3 = (0, import_node_crypto6.createHash)("sha256");
28772
+ const stream = import_node_fs3.default.createReadStream(filePath);
28773
+ stream.on("data", (chunk) => hash3.update(chunk));
28774
+ stream.on("end", () => resolve2(hash3.digest("hex")));
28775
+ stream.on("error", reject);
28776
+ });
28777
+ }
28778
+ async function downloadAndExtract(bundleUrl) {
28779
+ import_node_fs3.default.rmSync(STAGING_DIR, { recursive: true, force: true });
28780
+ import_node_fs3.default.mkdirSync(STAGING_DIR, { recursive: true });
28781
+ const tarballPath = import_node_path3.default.join(STAGING_DIR, "update.tar.gz");
28782
+ const response = await followRedirects(bundleUrl);
28783
+ const fileStream = import_node_fs3.default.createWriteStream(tarballPath);
28784
+ await (0, import_promises.pipeline)(response, fileStream);
28785
+ await extractTarGz(tarballPath, STAGING_DIR);
28786
+ import_node_fs3.default.unlinkSync(tarballPath);
28787
+ }
28788
+ async function extractTarGz(tarballPath, destDir) {
28789
+ const { execSync: execSync5 } = await import("node:child_process");
28790
+ execSync5(`tar xzf ${JSON.stringify(tarballPath)} -C ${JSON.stringify(destDir)}`, { stdio: "ignore" });
28791
+ }
28792
+ async function verifyUpdate(dir) {
28793
+ const versionPath = import_node_path3.default.join(dir, "version.json");
28794
+ if (!import_node_fs3.default.existsSync(versionPath)) {
28795
+ throw new Error("Missing version.json in update bundle");
28796
+ }
28797
+ const info = JSON.parse(import_node_fs3.default.readFileSync(versionPath, "utf-8"));
28798
+ if (!info.version) {
28799
+ throw new Error("Invalid version.json: missing version field");
28800
+ }
28801
+ if (info.checksums) {
28802
+ for (const [relativePath, expectedHash] of Object.entries(info.checksums)) {
28803
+ const filePath = import_node_path3.default.join(dir, relativePath);
28804
+ if (!import_node_fs3.default.existsSync(filePath)) {
28805
+ throw new Error(`Missing file in update: ${relativePath}`);
28806
+ }
28807
+ const actualHash = await sha256File(filePath);
28808
+ if (actualHash !== expectedHash) {
28809
+ throw new Error(`Checksum mismatch for ${relativePath}`);
28810
+ }
28811
+ }
28812
+ }
28813
+ const requiredFiles = ["lib/cli.js", "lib/api-server.js", "lib/server.js"];
28814
+ for (const f of requiredFiles) {
28815
+ if (!import_node_fs3.default.existsSync(import_node_path3.default.join(dir, f))) {
28816
+ throw new Error(`Missing required file: ${f}`);
28817
+ }
28818
+ }
28819
+ return info;
28820
+ }
28821
+ function applyUpdate() {
28822
+ import_node_fs3.default.rmSync(USER_APP_DIR, { recursive: true, force: true });
28823
+ import_node_fs3.default.renameSync(STAGING_DIR, USER_APP_DIR);
28824
+ }
28825
+ function semverGt(a, b) {
28826
+ const pa = a.split(".").map(Number);
28827
+ const pb = b.split(".").map(Number);
28828
+ for (let i = 0; i < 3; i++) {
28829
+ const av = pa[i] ?? 0;
28830
+ const bv = pb[i] ?? 0;
28831
+ if (av > bv) return true;
28832
+ if (av < bv) return false;
28833
+ }
28834
+ return false;
28835
+ }
28836
+ function getCurrentVersion() {
28837
+ try {
28838
+ return true ? "0.1.21" : null.version;
28839
+ } catch {
28840
+ return "0.0.0";
28841
+ }
28842
+ }
28843
+ function getReadyUpdateVersion() {
28844
+ try {
28845
+ if (!import_node_fs3.default.existsSync(VERSION_FILE)) return null;
28846
+ const info = JSON.parse(import_node_fs3.default.readFileSync(VERSION_FILE, "utf-8"));
28847
+ if (info.version && semverGt(info.version, getCurrentVersion())) {
28848
+ return info.version;
28849
+ }
28850
+ return null;
28851
+ } catch {
28852
+ return null;
28853
+ }
28854
+ }
28855
+ async function checkAndApplyUpdate(bundleUrl, targetVersion) {
28856
+ if (downloadInProgress) return;
28857
+ const readyVersion = getReadyUpdateVersion();
28858
+ if (readyVersion && !semverGt(targetVersion, readyVersion)) return;
28859
+ downloadInProgress = true;
28860
+ try {
28861
+ console.error(`[auto-update] Downloading update v${targetVersion}...`);
28862
+ status = { state: "downloading", version: targetVersion };
28863
+ await downloadAndExtract(bundleUrl);
28864
+ console.error(`[auto-update] Verifying update v${targetVersion}...`);
28865
+ status = { state: "verifying", version: targetVersion };
28866
+ const info = await verifyUpdate(STAGING_DIR);
28867
+ if (info.minEngineVersion) {
28868
+ const currentVersion = getCurrentVersion();
28869
+ if (semverGt(info.minEngineVersion, currentVersion)) {
28870
+ console.error(`[auto-update] Update requires engine >= ${info.minEngineVersion}, current is ${currentVersion}. Skipping.`);
28871
+ import_node_fs3.default.rmSync(STAGING_DIR, { recursive: true, force: true });
28872
+ status = { state: "error", error: `Requires full installer (engine >= ${info.minEngineVersion})` };
28873
+ return;
28874
+ }
28875
+ }
28876
+ console.error(`[auto-update] Applying update v${targetVersion}...`);
28877
+ applyUpdate();
28878
+ console.error(`[auto-update] Update v${targetVersion} ready! Restart to activate.`);
28879
+ status = { state: "ready", version: targetVersion };
28880
+ } catch (err) {
28881
+ const message = err instanceof Error ? err.message : "Unknown error";
28882
+ console.error(`[auto-update] Failed: ${message}`);
28883
+ status = { state: "error", error: message };
28884
+ import_node_fs3.default.rmSync(STAGING_DIR, { recursive: true, force: true });
28885
+ } finally {
28886
+ downloadInProgress = false;
28887
+ }
28888
+ }
28889
+
28890
+ // src/server/updateChecker.ts
28646
28891
  var CHECK_INTERVAL = 4 * 60 * 60 * 1e3;
28647
28892
  var INITIAL_DELAY = 15e3;
28648
28893
  var cached = null;
@@ -28661,7 +28906,7 @@ function pickLatestStable(releases) {
28661
28906
  }
28662
28907
  function fetchJson(url) {
28663
28908
  return new Promise((resolve2, reject) => {
28664
- const req = import_node_https.default.get(url, { headers: { "User-Agent": "quoroom-update-checker" } }, (res) => {
28909
+ const req = import_node_https2.default.get(url, { headers: { "User-Agent": "quoroom-update-checker" } }, (res) => {
28665
28910
  const chunks = [];
28666
28911
  res.on("data", (c) => chunks.push(c));
28667
28912
  res.on("end", () => {
@@ -28689,13 +28934,19 @@ async function forceCheck() {
28689
28934
  if (!latest?.assets) return;
28690
28935
  const latestVersion = latest.tag_name.replace(/^v/, "");
28691
28936
  const assets = { mac: null, windows: null, linux: null };
28937
+ let updateBundle = null;
28692
28938
  for (const a of latest.assets) {
28693
28939
  const { name, browser_download_url: url } = a;
28694
28940
  if (name.endsWith(".pkg")) assets.mac = url;
28695
28941
  else if (name.toLowerCase().includes("setup") && name.endsWith(".exe")) assets.windows = url;
28696
28942
  else if (name.endsWith(".deb")) assets.linux = url;
28943
+ else if (name.startsWith("quoroom-update-") && name.endsWith(".tar.gz")) updateBundle = url;
28944
+ }
28945
+ cached = { latestVersion, releaseUrl: latest.html_url, assets, updateBundle };
28946
+ if (updateBundle && latestVersion) {
28947
+ void checkAndApplyUpdate(updateBundle, latestVersion).catch(() => {
28948
+ });
28697
28949
  }
28698
- cached = { latestVersion, releaseUrl: latest.html_url, assets };
28699
28950
  } catch {
28700
28951
  }
28701
28952
  }
@@ -28730,7 +28981,8 @@ async function simulateUpdate() {
28730
28981
  mac: cached?.assets.mac ?? null,
28731
28982
  windows: cached?.assets.windows ?? null,
28732
28983
  linux: cached?.assets.linux ?? null
28733
- }
28984
+ },
28985
+ updateBundle: cached?.updateBundle ?? null
28734
28986
  };
28735
28987
  }
28736
28988
 
@@ -28740,7 +28992,7 @@ var cachedVersion = null;
28740
28992
  function getVersion3() {
28741
28993
  if (cachedVersion) return cachedVersion;
28742
28994
  try {
28743
- cachedVersion = true ? "0.1.19" : null.version;
28995
+ cachedVersion = true ? "0.1.21" : null.version;
28744
28996
  } catch {
28745
28997
  cachedVersion = "unknown";
28746
28998
  }
@@ -28811,11 +29063,11 @@ function warmStatusCaches() {
28811
29063
  }
28812
29064
  warmStatusCaches();
28813
29065
  function getResources() {
28814
- const [load1, load5] = import_node_os3.default.loadavg();
28815
- const total = import_node_os3.default.totalmem();
28816
- const free = import_node_os3.default.freemem();
29066
+ const [load1, load5] = import_node_os4.default.loadavg();
29067
+ const total = import_node_os4.default.totalmem();
29068
+ const free = import_node_os4.default.freemem();
28817
29069
  return {
28818
- cpuCount: import_node_os3.default.cpus().length,
29070
+ cpuCount: import_node_os4.default.cpus().length,
28819
29071
  loadAvg1m: Math.round(load1 * 100) / 100,
28820
29072
  loadAvg5m: Math.round(load5 * 100) / 100,
28821
29073
  memTotalGb: Math.round(total / 1024 / 1024 / 1024 * 10) / 10,
@@ -28828,6 +29080,12 @@ function registerStatusRoutes(router) {
28828
29080
  await simulateUpdate();
28829
29081
  return { data: { ok: true } };
28830
29082
  });
29083
+ router.post("/api/status/test-auto-update", async (ctx) => {
29084
+ const { url, version: version5 } = ctx.body ?? {};
29085
+ if (!url || !version5) return { error: "Missing url or version", status: 400 };
29086
+ await checkAndApplyUpdate(url, version5);
29087
+ return { data: { status: getAutoUpdateStatus(), readyVersion: getReadyUpdateVersion() } };
29088
+ });
28831
29089
  router.post("/api/status/check-update", async () => {
28832
29090
  await forceCheck();
28833
29091
  return { data: { updateInfo: getUpdateInfo() } };
@@ -28862,6 +29120,8 @@ function registerStatusRoutes(router) {
28862
29120
  }
28863
29121
  if (include("update")) {
28864
29122
  data.updateInfo = getUpdateInfo();
29123
+ data.autoUpdate = getAutoUpdateStatus();
29124
+ data.readyUpdateVersion = getReadyUpdateVersion();
28865
29125
  }
28866
29126
  if (Object.keys(pending).length > 0) {
28867
29127
  data.pending = pending;
@@ -28873,6 +29133,7 @@ function registerStatusRoutes(router) {
28873
29133
  }
28874
29134
 
28875
29135
  // src/server/routes/wallet.ts
29136
+ var import_node_crypto7 = __toESM(require("node:crypto"));
28876
29137
  init_cloud_sync();
28877
29138
  function parseLimit3(raw, fallback, max) {
28878
29139
  const n = Number(raw);
@@ -28908,9 +29169,11 @@ async function fetchRoomBalance(roomId, address) {
28908
29169
  );
28909
29170
  const byChain = {};
28910
29171
  let totalBalance = 0;
29172
+ let anySuccess = false;
28911
29173
  for (const { chain, token, result } of results) {
28912
29174
  if (!byChain[chain]) byChain[chain] = { usdc: 0, usdt: 0, total: 0 };
28913
29175
  if (result.ok) {
29176
+ anySuccess = true;
28914
29177
  byChain[chain][token] = result.balance;
28915
29178
  byChain[chain].total += result.balance;
28916
29179
  totalBalance += result.balance;
@@ -28922,7 +29185,9 @@ async function fetchRoomBalance(roomId, address) {
28922
29185
  address,
28923
29186
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
28924
29187
  };
28925
- balanceCache.set(roomId, { data, fetchedAt: Date.now() });
29188
+ if (anySuccess) {
29189
+ balanceCache.set(roomId, { data, fetchedAt: Date.now() });
29190
+ }
28926
29191
  return data;
28927
29192
  })();
28928
29193
  pendingBalanceRequests.set(roomId, request);
@@ -28959,6 +29224,9 @@ function registerWalletRoutes(router) {
28959
29224
  }
28960
29225
  try {
28961
29226
  const data = await fetchRoomBalance(roomId, wallet.address);
29227
+ if (data.totalBalance === 0 && Object.keys(data.byChain).length === 0 && cached2 && cached2.data.totalBalance > 0) {
29228
+ return { data: { ...cached2.data, fetchedAt: (/* @__PURE__ */ new Date()).toISOString() } };
29229
+ }
28962
29230
  return { data };
28963
29231
  } catch {
28964
29232
  if (cached2) {
@@ -28994,6 +29262,73 @@ function registerWalletRoutes(router) {
28994
29262
  if (!result) return { status: 503, error: "On-ramp unavailable" };
28995
29263
  return { data: result };
28996
29264
  });
29265
+ router.post("/api/rooms/:roomId/wallet/withdraw", async (ctx) => {
29266
+ const roomId = Number(ctx.params.roomId);
29267
+ const room = getRoom(ctx.db, roomId);
29268
+ if (!room) return { status: 404, error: "Room not found" };
29269
+ const wallet = getWalletByRoom(ctx.db, roomId);
29270
+ if (!wallet) return { status: 400, error: "Room has no wallet" };
29271
+ const { to: rawTo, amount: rawAmount, chain, token } = ctx.body;
29272
+ const to = rawTo?.trim();
29273
+ const amount = rawAmount?.trim();
29274
+ if (!to || !amount) return { status: 400, error: "Missing required fields: to, amount" };
29275
+ if (!/^0x[0-9a-fA-F]{40}$/.test(to)) return { status: 400, error: "Invalid address" };
29276
+ const parsed = parseFloat(amount);
29277
+ if (!Number.isFinite(parsed) || parsed <= 0) return { status: 400, error: "Invalid amount" };
29278
+ const selectedChain = chain ?? "base";
29279
+ const selectedToken = token ?? "usdc";
29280
+ const chainConfig2 = CHAIN_CONFIGS[selectedChain];
29281
+ if (!chainConfig2) return { status: 400, error: `Unsupported chain: ${selectedChain}` };
29282
+ const tokenConfig = chainConfig2.tokens[selectedToken];
29283
+ if (!tokenConfig) return { status: 400, error: `Token ${selectedToken} not available on ${selectedChain}` };
29284
+ const encryptionKey = import_node_crypto7.default.createHash("sha256").update(`quoroom-wallet-${room.id}-${room.name}`).digest("hex");
29285
+ const GAS_TIPS = {
29286
+ base: { token: "ETH", amount: "0.0001 ETH (~$0.25)" },
29287
+ arbitrum: { token: "ETH", amount: "0.0001 ETH (~$0.25)" },
29288
+ optimism: { token: "ETH", amount: "0.0001 ETH (~$0.25)" },
29289
+ ethereum: { token: "ETH", amount: "0.002 ETH (~$5)" },
29290
+ polygon: { token: "POL", amount: "0.1 POL (~$0.05)" }
29291
+ };
29292
+ const gasTip = GAS_TIPS[selectedChain] ?? { token: "ETH", amount: "0.001 ETH" };
29293
+ const VIEM_CHAINS2 = {
29294
+ base,
29295
+ ethereum: mainnet,
29296
+ arbitrum,
29297
+ optimism,
29298
+ polygon
29299
+ };
29300
+ try {
29301
+ const viemChain = VIEM_CHAINS2[selectedChain];
29302
+ if (viemChain) {
29303
+ const publicClient = createPublicClient({ chain: viemChain, transport: http(chainConfig2.rpcUrl) });
29304
+ const gasBalance = await publicClient.getBalance({ address: wallet.address });
29305
+ if (gasBalance === 0n) {
29306
+ return { status: 400, error: `No ${gasTip.token} for gas fees. Send at least ${gasTip.amount} to ${wallet.address} on ${chainConfig2.name} to cover the transaction.` };
29307
+ }
29308
+ }
29309
+ } catch {
29310
+ }
29311
+ try {
29312
+ const txHash = await sendToken(
29313
+ ctx.db,
29314
+ roomId,
29315
+ to,
29316
+ amount,
29317
+ encryptionKey,
29318
+ selectedChain,
29319
+ tokenConfig.address,
29320
+ tokenConfig.decimals
29321
+ );
29322
+ balanceCache.delete(roomId);
29323
+ return { data: { txHash } };
29324
+ } catch (e) {
29325
+ const msg = e.message || "Unknown error";
29326
+ if (msg.includes("gas") || msg.includes("insufficient funds")) {
29327
+ return { status: 400, error: `Insufficient ${gasTip.token} for gas fees on ${chainConfig2.name}. Send at least ${gasTip.amount} to your wallet address to cover the transaction.` };
29328
+ }
29329
+ return { status: 400, error: `Withdraw failed: ${msg.split("\n")[0]}` };
29330
+ }
29331
+ });
28997
29332
  }
28998
29333
 
28999
29334
  // src/server/routes/credentials.ts
@@ -29303,8 +29638,8 @@ function registerStationRoutes(router) {
29303
29638
  function registerRoomMessageRoutes(router) {
29304
29639
  router.get("/api/rooms/:roomId/messages", (ctx) => {
29305
29640
  const roomId = Number(ctx.params.roomId);
29306
- const status = ctx.query.status;
29307
- return { data: listRoomMessages(ctx.db, roomId, status) };
29641
+ const status2 = ctx.query.status;
29642
+ return { data: listRoomMessages(ctx.db, roomId, status2) };
29308
29643
  });
29309
29644
  router.post("/api/rooms/:roomId/messages", (ctx) => {
29310
29645
  const roomId = Number(ctx.params.roomId);
@@ -29372,7 +29707,7 @@ function registerRoomMessageRoutes(router) {
29372
29707
 
29373
29708
  // src/server/provider-auth.ts
29374
29709
  var import_node_child_process3 = require("node:child_process");
29375
- var import_node_crypto6 = require("node:crypto");
29710
+ var import_node_crypto8 = require("node:crypto");
29376
29711
  var sessionStore = /* @__PURE__ */ new Map();
29377
29712
  var activeByProvider = /* @__PURE__ */ new Map();
29378
29713
  var MAX_LINES = Math.max(50, parseInt(process.env.QUOROOM_PROVIDER_AUTH_MAX_LINES || "300", 10) || 300);
@@ -29385,8 +29720,8 @@ function getProviderCommand(provider) {
29385
29720
  if (provider === "codex") return { command: "codex", args: ["login"] };
29386
29721
  return { command: "claude", args: ["login"] };
29387
29722
  }
29388
- function isActiveStatus(status) {
29389
- return status === "starting" || status === "running";
29723
+ function isActiveStatus(status2) {
29724
+ return status2 === "starting" || status2 === "running";
29390
29725
  }
29391
29726
  function emitSessionStatus(session) {
29392
29727
  eventBus.emit(`provider-auth:${session.sessionId}`, "provider_auth:status", toSessionView(session, false));
@@ -29493,11 +29828,11 @@ function toSessionView(session, includeLines = true) {
29493
29828
  lines: includeLines ? [...session.lines] : []
29494
29829
  };
29495
29830
  }
29496
- function finalizeSession(session, status, exitCode) {
29831
+ function finalizeSession(session, status2, exitCode) {
29497
29832
  if (!isActiveStatus(session.status)) return;
29498
29833
  clearTimeout(session.timeout);
29499
29834
  flushBufferedLines(session);
29500
- session.status = status;
29835
+ session.status = status2;
29501
29836
  session.exitCode = exitCode;
29502
29837
  session.endedAt = nowIso();
29503
29838
  session.updatedAt = session.endedAt;
@@ -29590,7 +29925,7 @@ function startProviderAuthSession(provider) {
29590
29925
  });
29591
29926
  const startedAt2 = nowIso();
29592
29927
  const session = {
29593
- sessionId: (0, import_node_crypto6.randomUUID)(),
29928
+ sessionId: (0, import_node_crypto8.randomUUID)(),
29594
29929
  provider,
29595
29930
  command: displayCommand,
29596
29931
  status: "starting",
@@ -29657,8 +29992,8 @@ function startProviderAuthSession(provider) {
29657
29992
 
29658
29993
  // src/server/provider-install.ts
29659
29994
  var import_node_child_process5 = require("node:child_process");
29660
- var import_node_crypto7 = require("node:crypto");
29661
- var import_node_path3 = __toESM(require("node:path"));
29995
+ var import_node_crypto9 = require("node:crypto");
29996
+ var import_node_path4 = __toESM(require("node:path"));
29662
29997
 
29663
29998
  // src/server/provider-cli.ts
29664
29999
  var import_node_child_process4 = require("node:child_process");
@@ -29681,6 +30016,9 @@ function probeProviderInstalled(provider) {
29681
30016
  return out.ok ? { installed: true, version: out.stdout || void 0 } : { installed: false };
29682
30017
  }
29683
30018
  function probeProviderConnected(provider) {
30019
+ if (provider === "claude") {
30020
+ return probeProviderInstalled("claude").installed ? true : null;
30021
+ }
29684
30022
  const attempts = provider === "codex" ? [["login", "status"], ["auth", "status"]] : [["auth", "status"], ["login", "status"]];
29685
30023
  for (const args of attempts) {
29686
30024
  const out = safeExec(provider, args);
@@ -29726,14 +30064,14 @@ function addGlobalNpmBinToPath(platform = process.platform) {
29726
30064
  const npmBin = (0, import_node_child_process5.execFileSync)(npmCommand, ["bin", "-g"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
29727
30065
  if (!npmBin) return;
29728
30066
  const currentPath = process.env.PATH || "";
29729
- const parts = currentPath.split(import_node_path3.default.delimiter).filter(Boolean);
30067
+ const parts = currentPath.split(import_node_path4.default.delimiter).filter(Boolean);
29730
30068
  if (parts.includes(npmBin)) return;
29731
- process.env.PATH = `${npmBin}${import_node_path3.default.delimiter}${currentPath}`;
30069
+ process.env.PATH = `${npmBin}${import_node_path4.default.delimiter}${currentPath}`;
29732
30070
  } catch {
29733
30071
  }
29734
30072
  }
29735
- function isActiveStatus2(status) {
29736
- return status === "starting" || status === "running";
30073
+ function isActiveStatus2(status2) {
30074
+ return status2 === "starting" || status2 === "running";
29737
30075
  }
29738
30076
  function toSessionView2(session, includeLines = true) {
29739
30077
  return {
@@ -29797,11 +30135,11 @@ function flushBufferedLines2(session) {
29797
30135
  session.stdoutBuffer = "";
29798
30136
  session.stderrBuffer = "";
29799
30137
  }
29800
- function finalizeSession2(session, status, exitCode) {
30138
+ function finalizeSession2(session, status2, exitCode) {
29801
30139
  if (!isActiveStatus2(session.status)) return;
29802
30140
  clearTimeout(session.timeout);
29803
30141
  flushBufferedLines2(session);
29804
- session.status = status;
30142
+ session.status = status2;
29805
30143
  session.exitCode = exitCode;
29806
30144
  session.endedAt = nowIso2();
29807
30145
  session.updatedAt = session.endedAt;
@@ -29890,7 +30228,7 @@ function startProviderInstallSession(provider) {
29890
30228
  });
29891
30229
  const startedAt2 = nowIso2();
29892
30230
  const session = {
29893
- sessionId: (0, import_node_crypto7.randomUUID)(),
30231
+ sessionId: (0, import_node_crypto9.randomUUID)(),
29894
30232
  provider,
29895
30233
  command: displayCommand,
29896
30234
  status: "starting",
@@ -30340,15 +30678,15 @@ async function handleWebhookRequest(pathname, body, db2) {
30340
30678
 
30341
30679
  // src/server/shell-path.ts
30342
30680
  var import_node_child_process6 = require("node:child_process");
30343
- var import_node_fs3 = require("node:fs");
30344
- var import_node_path4 = __toESM(require("node:path"));
30681
+ var import_node_fs4 = require("node:fs");
30682
+ var import_node_path5 = __toESM(require("node:path"));
30345
30683
  function inheritShellPath() {
30346
30684
  if (process.platform !== "darwin") return;
30347
30685
  const currentPath = process.env.PATH || "";
30348
- const currentParts = new Set(currentPath.split(import_node_path4.default.delimiter).filter(Boolean));
30686
+ const currentParts = new Set(currentPath.split(import_node_path5.default.delimiter).filter(Boolean));
30349
30687
  const shells = [process.env.SHELL, "/bin/zsh", "/bin/bash"].filter(Boolean);
30350
30688
  for (const sh of shells) {
30351
- if (!(0, import_node_fs3.existsSync)(sh)) continue;
30689
+ if (!(0, import_node_fs4.existsSync)(sh)) continue;
30352
30690
  try {
30353
30691
  const env = { ...process.env };
30354
30692
  delete env.ELECTRON_RUN_AS_NODE;
@@ -30359,7 +30697,7 @@ function inheritShellPath() {
30359
30697
  stdio: ["ignore", "pipe", "ignore"]
30360
30698
  }).trim();
30361
30699
  if (!shellPath) continue;
30362
- const newParts = shellPath.split(import_node_path4.default.delimiter).filter(Boolean);
30700
+ const newParts = shellPath.split(import_node_path5.default.delimiter).filter(Boolean);
30363
30701
  const additions = [];
30364
30702
  for (const p of newParts) {
30365
30703
  if (!currentParts.has(p)) {
@@ -30368,7 +30706,7 @@ function inheritShellPath() {
30368
30706
  }
30369
30707
  }
30370
30708
  if (additions.length > 0) {
30371
- process.env.PATH = `${currentPath}${import_node_path4.default.delimiter}${additions.join(import_node_path4.default.delimiter)}`;
30709
+ process.env.PATH = `${currentPath}${import_node_path5.default.delimiter}${additions.join(import_node_path5.default.delimiter)}`;
30372
30710
  }
30373
30711
  return;
30374
30712
  } catch {
@@ -30394,7 +30732,7 @@ function streamWithRedirects(url, res, corsHeaders, filename, depth = 0) {
30394
30732
  }
30395
30733
  try {
30396
30734
  const parsed = new import_node_url.URL(url);
30397
- const mod2 = parsed.protocol === "https:" ? import_node_https2.default : import_node_http.default;
30735
+ const mod2 = parsed.protocol === "https:" ? import_node_https3.default : import_node_http2.default;
30398
30736
  mod2.get(url, { headers: { "User-Agent": "quoroom-updater/1.0" } }, (assetRes) => {
30399
30737
  if ((assetRes.statusCode === 301 || assetRes.statusCode === 302 || assetRes.statusCode === 307) && assetRes.headers.location) {
30400
30738
  assetRes.resume();
@@ -30542,7 +30880,7 @@ function maybeLogHttpProfile(method, pathname, statusCode, durationMs) {
30542
30880
  }
30543
30881
  function getCacheControl(filePath, ext) {
30544
30882
  const normalized = filePath.replace(/\\/g, "/");
30545
- const base2 = import_node_path5.default.basename(filePath);
30883
+ const base2 = import_node_path6.default.basename(filePath);
30546
30884
  if (base2 === "sw.js") return "no-cache, no-store, must-revalidate";
30547
30885
  if (ext === ".html") return "no-cache, no-store, must-revalidate";
30548
30886
  if (ext === ".webmanifest") return "public, max-age=3600";
@@ -30561,15 +30899,15 @@ function getCacheControl(filePath, ext) {
30561
30899
  return "no-cache, max-age=0";
30562
30900
  }
30563
30901
  function serveStatic(staticDir, pathname, res) {
30564
- const safePath = import_node_path5.default.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
30565
- let filePath = import_node_path5.default.join(staticDir, safePath);
30902
+ const safePath = import_node_path6.default.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
30903
+ let filePath = import_node_path6.default.join(staticDir, safePath);
30566
30904
  try {
30567
- const stat = import_node_fs4.default.statSync(filePath);
30905
+ const stat = import_node_fs5.default.statSync(filePath);
30568
30906
  if (stat.isDirectory()) {
30569
- filePath = import_node_path5.default.join(filePath, "index.html");
30907
+ filePath = import_node_path6.default.join(filePath, "index.html");
30570
30908
  }
30571
30909
  } catch {
30572
- if (import_node_path5.default.extname(safePath)) {
30910
+ if (import_node_path6.default.extname(safePath)) {
30573
30911
  res.writeHead(404, {
30574
30912
  "Content-Type": "text/plain; charset=utf-8",
30575
30913
  "Cache-Control": "no-cache, no-store, must-revalidate"
@@ -30577,15 +30915,15 @@ function serveStatic(staticDir, pathname, res) {
30577
30915
  res.end("Not Found");
30578
30916
  return;
30579
30917
  }
30580
- filePath = import_node_path5.default.join(staticDir, "index.html");
30918
+ filePath = import_node_path6.default.join(staticDir, "index.html");
30581
30919
  }
30582
- const ext = import_node_path5.default.extname(filePath).toLowerCase();
30920
+ const ext = import_node_path6.default.extname(filePath).toLowerCase();
30583
30921
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
30584
30922
  const headers = {
30585
30923
  "Content-Type": contentType,
30586
30924
  "Cache-Control": getCacheControl(filePath, ext)
30587
30925
  };
30588
- const stream = import_node_fs4.default.createReadStream(filePath);
30926
+ const stream = import_node_fs5.default.createReadStream(filePath);
30589
30927
  stream.on("open", () => {
30590
30928
  res.writeHead(200, headers);
30591
30929
  stream.pipe(res);
@@ -30640,7 +30978,7 @@ function createApiServer(options = {}) {
30640
30978
  if (!options.skipTokenFile) {
30641
30979
  writeTokenFile(dataDir, token, port);
30642
30980
  }
30643
- const server = import_node_http.default.createServer(async (req, res) => {
30981
+ const server = import_node_http2.default.createServer(async (req, res) => {
30644
30982
  res.on("error", () => {
30645
30983
  });
30646
30984
  const url = new import_node_url.URL(req.url, `http://${req.headers.host || "localhost"}`);
@@ -30729,6 +31067,30 @@ function createApiServer(options = {}) {
30729
31067
  setTimeout(() => process.exit(0), 120);
30730
31068
  return;
30731
31069
  }
31070
+ if (pathname === "/api/server/update-restart" && req.method === "POST") {
31071
+ const isLocalClient = isLoopbackAddress(req.socket.remoteAddress);
31072
+ if (!isLocalClient || origin && !isLocalOrigin(origin)) {
31073
+ res.writeHead(403, responseHeaders);
31074
+ res.end(JSON.stringify({ error: "Update-restart allowed only from localhost clients" }));
31075
+ return;
31076
+ }
31077
+ const readyVersion = getReadyUpdateVersion();
31078
+ if (!readyVersion) {
31079
+ res.writeHead(404, responseHeaders);
31080
+ res.end(JSON.stringify({ error: "No update ready to apply" }));
31081
+ return;
31082
+ }
31083
+ const scheduled = scheduleSelfRestart();
31084
+ if (!scheduled) {
31085
+ res.writeHead(500, responseHeaders);
31086
+ res.end(JSON.stringify({ error: "Failed to schedule restart" }));
31087
+ return;
31088
+ }
31089
+ res.writeHead(202, responseHeaders);
31090
+ res.end(JSON.stringify({ ok: true, restarting: true, version: readyVersion }));
31091
+ setTimeout(() => process.exit(0), 120);
31092
+ return;
31093
+ }
30732
31094
  if (pathname === "/api/auth/verify" && req.method === "GET") {
30733
31095
  const principal = getTokenPrincipal(req.headers.authorization);
30734
31096
  if (!principal) {
@@ -30816,13 +31178,13 @@ function createApiServer(options = {}) {
30816
31178
  res.end();
30817
31179
  return;
30818
31180
  }
30819
- const status = result.error ? result.status || 400 : result.status || 200;
30820
- res.writeHead(status, responseHeaders);
31181
+ const status2 = result.error ? result.status || 400 : result.status || 200;
31182
+ res.writeHead(status2, responseHeaders);
30821
31183
  res.end(JSON.stringify(result.error ? { error: result.error } : result.data));
30822
31184
  } catch (err) {
30823
31185
  const message = err instanceof Error ? err.message : "Internal error";
30824
- const status = message === "Invalid JSON body" ? 400 : message === "Payload too large" ? 413 : 500;
30825
- res.writeHead(status, responseHeaders);
31186
+ const status2 = message === "Invalid JSON body" ? 400 : message === "Payload too large" ? 413 : 500;
31187
+ res.writeHead(status2, responseHeaders);
30826
31188
  res.end(JSON.stringify({ error: message }));
30827
31189
  }
30828
31190
  return;
@@ -30839,16 +31201,16 @@ function createApiServer(options = {}) {
30839
31201
  }
30840
31202
  function patchMcpConfig(configPath, entry) {
30841
31203
  try {
30842
- if (!import_node_fs4.default.existsSync(configPath)) return false;
31204
+ if (!import_node_fs5.default.existsSync(configPath)) return false;
30843
31205
  let config = {};
30844
31206
  try {
30845
- config = JSON.parse(import_node_fs4.default.readFileSync(configPath, "utf-8"));
31207
+ config = JSON.parse(import_node_fs5.default.readFileSync(configPath, "utf-8"));
30846
31208
  } catch {
30847
31209
  }
30848
31210
  const mcpServers = config.mcpServers ?? {};
30849
31211
  mcpServers["quoroom"] = entry;
30850
31212
  config.mcpServers = mcpServers;
30851
- import_node_fs4.default.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
31213
+ import_node_fs5.default.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
30852
31214
  return true;
30853
31215
  } catch {
30854
31216
  return false;
@@ -30856,8 +31218,8 @@ function patchMcpConfig(configPath, entry) {
30856
31218
  }
30857
31219
  function registerMcpGlobally(dbPath) {
30858
31220
  try {
30859
- const home = (0, import_node_os4.homedir)();
30860
- const mcpServerPath = import_node_path5.default.join(__dirname, "server.js");
31221
+ const home = (0, import_node_os5.homedir)();
31222
+ const mcpServerPath = import_node_path6.default.join(__dirname, "server.js");
30861
31223
  const nodePath = process.execPath;
30862
31224
  const entry = (source) => ({
30863
31225
  command: nodePath,
@@ -30866,12 +31228,12 @@ function registerMcpGlobally(dbPath) {
30866
31228
  });
30867
31229
  const isWin = process.platform === "win32";
30868
31230
  const isMac = process.platform === "darwin";
30869
- patchMcpConfig(import_node_path5.default.join(home, ".claude.json"), entry("claude-code"));
30870
- const claudeDesktopPath = isWin ? import_node_path5.default.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json") : isMac ? import_node_path5.default.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : import_node_path5.default.join(home, ".config", "Claude", "claude_desktop_config.json");
31231
+ patchMcpConfig(import_node_path6.default.join(home, ".claude.json"), entry("claude-code"));
31232
+ const claudeDesktopPath = isWin ? import_node_path6.default.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json") : isMac ? import_node_path6.default.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : import_node_path6.default.join(home, ".config", "Claude", "claude_desktop_config.json");
30871
31233
  patchMcpConfig(claudeDesktopPath, entry("claude-desktop"));
30872
- patchMcpConfig(import_node_path5.default.join(home, ".cursor", "mcp.json"), entry("cursor"));
31234
+ patchMcpConfig(import_node_path6.default.join(home, ".cursor", "mcp.json"), entry("cursor"));
30873
31235
  patchMcpConfig(
30874
- import_node_path5.default.join(home, ".codeium", "windsurf", "mcp_config.json"),
31236
+ import_node_path6.default.join(home, ".codeium", "windsurf", "mcp_config.json"),
30875
31237
  entry("windsurf")
30876
31238
  );
30877
31239
  } catch {
@@ -30883,18 +31245,22 @@ function startServer(options = {}) {
30883
31245
  const deploymentMode = getDeploymentMode();
30884
31246
  const bindHost = process.env.QUOROOM_BIND_HOST || (deploymentMode === "cloud" ? DEFAULT_BIND_HOST_CLOUD : DEFAULT_BIND_HOST_LOCAL);
30885
31247
  if (!options.staticDir) {
30886
- const defaultUiDir = import_node_path5.default.join(__dirname, "../ui");
30887
- if (import_node_fs4.default.existsSync(defaultUiDir)) {
30888
- options.staticDir = defaultUiDir;
31248
+ const userUiDir = import_node_path6.default.join(USER_APP_DIR, "ui");
31249
+ const bundledUiDir = import_node_path6.default.join(__dirname, "../ui");
31250
+ if (import_node_fs5.default.existsSync(import_node_path6.default.join(userUiDir, "index.html"))) {
31251
+ options.staticDir = userUiDir;
31252
+ } else if (import_node_fs5.default.existsSync(bundledUiDir)) {
31253
+ options.staticDir = bundledUiDir;
30889
31254
  }
30890
31255
  }
30891
- const dbPath = process.env.QUOROOM_DB_PATH || import_node_path5.default.join(options.dataDir ?? getDataDir(), "data.db");
31256
+ const dbPath = process.env.QUOROOM_DB_PATH || import_node_path6.default.join(options.dataDir ?? getDataDir(), "data.db");
30892
31257
  const { server, token, db: serverDb } = createApiServer(options);
30893
31258
  if (!process.env.QUOROOM_SKIP_MCP_REGISTER) {
30894
31259
  registerMcpGlobally(dbPath);
30895
31260
  }
30896
31261
  initCloudSync(serverDb);
30897
31262
  initUpdateChecker();
31263
+ initBootHealthCheck();
30898
31264
  startServerRuntime(serverDb);
30899
31265
  function listen() {
30900
31266
  server.listen(port, bindHost, () => {