quoroom 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/mcp/server.js CHANGED
@@ -3486,7 +3486,7 @@ var require_schemes = __commonJS({
3486
3486
  urnComponent.nss = (uuidComponent.uuid || "").toLowerCase();
3487
3487
  return urnComponent;
3488
3488
  }
3489
- var http3 = (
3489
+ var http2 = (
3490
3490
  /** @type {SchemeHandler} */
3491
3491
  {
3492
3492
  scheme: "http",
@@ -3499,7 +3499,7 @@ var require_schemes = __commonJS({
3499
3499
  /** @type {SchemeHandler} */
3500
3500
  {
3501
3501
  scheme: "https",
3502
- domainHost: http3.domainHost,
3502
+ domainHost: http2.domainHost,
3503
3503
  parse: httpParse,
3504
3504
  serialize: httpSerialize
3505
3505
  }
@@ -3543,7 +3543,7 @@ var require_schemes = __commonJS({
3543
3543
  var SCHEMES = (
3544
3544
  /** @type {Record<SchemeName, SchemeHandler>} */
3545
3545
  {
3546
- http: http3,
3546
+ http: http2,
3547
3547
  https,
3548
3548
  ws,
3549
3549
  wss,
@@ -7811,7 +7811,7 @@ function createHasher(hashCons) {
7811
7811
  hashC.create = () => hashCons();
7812
7812
  return hashC;
7813
7813
  }
7814
- function randomBytes2(bytesLength = 32) {
7814
+ function randomBytes3(bytesLength = 32) {
7815
7815
  if (crypto6 && typeof crypto6.getRandomValues === "function") {
7816
7816
  return crypto6.getRandomValues(new Uint8Array(bytesLength));
7817
7817
  }
@@ -9622,7 +9622,7 @@ function weierstrass(curveDef) {
9622
9622
  function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
9623
9623
  if (["recovered", "canonical"].some((k) => k in opts))
9624
9624
  throw new Error("sign() legacy options not supported");
9625
- const { hash: hash3, randomBytes: randomBytes3 } = CURVE;
9625
+ const { hash: hash3, randomBytes: randomBytes4 } = CURVE;
9626
9626
  let { lowS, prehash, extraEntropy: ent } = opts;
9627
9627
  if (lowS == null)
9628
9628
  lowS = true;
@@ -9634,7 +9634,7 @@ function weierstrass(curveDef) {
9634
9634
  const d = normPrivateKeyToScalar(privateKey);
9635
9635
  const seedArgs = [int2octets(d), int2octets(h1int)];
9636
9636
  if (ent != null && ent !== false) {
9637
- const e = ent === true ? randomBytes3(Fp.BYTES) : ent;
9637
+ const e = ent === true ? randomBytes4(Fp.BYTES) : ent;
9638
9638
  seedArgs.push(ensureBytes("extraEntropy", e));
9639
9639
  }
9640
9640
  const seed = concatBytes2(...seedArgs);
@@ -9956,7 +9956,7 @@ function getHash(hash3) {
9956
9956
  return {
9957
9957
  hash: hash3,
9958
9958
  hmac: (key, ...msgs) => hmac(hash3, key, concatBytes(...msgs)),
9959
- randomBytes: randomBytes2
9959
+ randomBytes: randomBytes3
9960
9960
  };
9961
9961
  }
9962
9962
  function createCurve(curveDef, defHash) {
@@ -10189,7 +10189,7 @@ function challenge(...args) {
10189
10189
  function schnorrGetPublicKey(privateKey) {
10190
10190
  return schnorrGetExtPubKey(privateKey).bytes;
10191
10191
  }
10192
- function schnorrSign(message, privateKey, auxRand = randomBytes2(32)) {
10192
+ function schnorrSign(message, privateKey, auxRand = randomBytes3(32)) {
10193
10193
  const m = ensureBytes("message", message);
10194
10194
  const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey);
10195
10195
  const a = ensureBytes("auxRand", auxRand, 32);
@@ -31151,12 +31151,14 @@ CREATE TABLE IF NOT EXISTS rooms (
31151
31151
  visibility TEXT NOT NULL DEFAULT 'private',
31152
31152
  autonomy_mode TEXT NOT NULL DEFAULT 'auto',
31153
31153
  max_concurrent_tasks INTEGER NOT NULL DEFAULT 3,
31154
- worker_model TEXT NOT NULL DEFAULT 'ollama:llama3.2',
31154
+ worker_model TEXT NOT NULL DEFAULT 'claude',
31155
31155
  queen_cycle_gap_ms INTEGER NOT NULL DEFAULT 1800000,
31156
31156
  queen_max_turns INTEGER NOT NULL DEFAULT 3,
31157
31157
  queen_quiet_from TEXT,
31158
31158
  queen_quiet_until TEXT,
31159
31159
  config TEXT,
31160
+ webhook_token TEXT,
31161
+ queen_nickname TEXT,
31160
31162
  chat_session_id TEXT,
31161
31163
  referred_by_code TEXT,
31162
31164
  created_at DATETIME DEFAULT (datetime('now','localtime')),
@@ -31237,6 +31239,7 @@ CREATE TABLE IF NOT EXISTS tasks (
31237
31239
  cron_expression TEXT,
31238
31240
  trigger_type TEXT NOT NULL DEFAULT 'cron',
31239
31241
  trigger_config TEXT,
31242
+ webhook_token TEXT,
31240
31243
  executor TEXT NOT NULL DEFAULT 'claude_code',
31241
31244
  status TEXT NOT NULL DEFAULT 'active',
31242
31245
  last_run DATETIME,
@@ -31545,6 +31548,18 @@ CREATE TABLE IF NOT EXISTS cycle_logs (
31545
31548
  );
31546
31549
  CREATE INDEX IF NOT EXISTS idx_cycle_logs_seq ON cycle_logs(cycle_id, seq);
31547
31550
 
31551
+ -- Agent session continuity (persists conversation history across queen cycles for all model types)
31552
+ -- session_id: for CLI models (claude/codex) \u2014 passed as --resume to continue the native session
31553
+ -- messages_json: for API models \u2014 full conversation turns array (no system prompt), stored as JSON
31554
+ CREATE TABLE IF NOT EXISTS agent_sessions (
31555
+ worker_id INTEGER PRIMARY KEY REFERENCES workers(id) ON DELETE CASCADE,
31556
+ session_id TEXT,
31557
+ messages_json TEXT,
31558
+ model TEXT NOT NULL DEFAULT '',
31559
+ turn_count INTEGER NOT NULL DEFAULT 0,
31560
+ updated_at DATETIME DEFAULT (datetime('now','localtime'))
31561
+ );
31562
+
31548
31563
  -- Schema version tracking
31549
31564
  CREATE TABLE IF NOT EXISTS schema_version (
31550
31565
  version INTEGER PRIMARY KEY,
@@ -31553,19 +31568,6 @@ CREATE TABLE IF NOT EXISTS schema_version (
31553
31568
  INSERT OR IGNORE INTO schema_version (version) VALUES (1);
31554
31569
  `;
31555
31570
 
31556
- // src/shared/db-migrations.ts
31557
- function runMigrations(database, log = console.log) {
31558
- database.exec(SCHEMA);
31559
- if (!database.prepare("SELECT value FROM settings WHERE key = ?").get("keeper_referral_code")) {
31560
- const code = (0, import_crypto.randomBytes)(6).toString("base64url").slice(0, 10);
31561
- database.prepare(
31562
- `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now','localtime'))
31563
- ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
31564
- ).run("keeper_referral_code", code);
31565
- }
31566
- log("Database schema initialized");
31567
- }
31568
-
31569
31571
  // src/shared/constants.ts
31570
31572
  var APP_NAME = "Quoroom";
31571
31573
  var DEFAULTS = {
@@ -31579,6 +31581,12 @@ var DEFAULTS = {
31579
31581
  WINDOW_HEIGHT_LARGE: 1200,
31580
31582
  PROGRESS_THROTTLE_MS: 2e3
31581
31583
  };
31584
+ var TRIGGER_TYPES = {
31585
+ CRON: "cron",
31586
+ ONCE: "once",
31587
+ MANUAL: "manual",
31588
+ WEBHOOK: "webhook"
31589
+ };
31582
31590
  var TASK_STATUSES = {
31583
31591
  ACTIVE: "active",
31584
31592
  PAUSED: "paused",
@@ -31862,8 +31870,8 @@ function mapWorkerRow(row) {
31862
31870
  }
31863
31871
  function createTask(db2, input) {
31864
31872
  const result = db2.prepare(
31865
- `INSERT INTO tasks (name, description, prompt, cron_expression, trigger_type, trigger_config, scheduled_at, executor, max_runs, worker_id, session_continuity, timeout_minutes, max_turns, allowed_tools, disallowed_tools, room_id)
31866
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
31873
+ `INSERT INTO tasks (name, description, prompt, cron_expression, trigger_type, trigger_config, webhook_token, scheduled_at, executor, max_runs, worker_id, session_continuity, timeout_minutes, max_turns, allowed_tools, disallowed_tools, room_id)
31874
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
31867
31875
  ).run(
31868
31876
  input.name,
31869
31877
  input.description ?? null,
@@ -31871,6 +31879,7 @@ function createTask(db2, input) {
31871
31879
  input.cronExpression ?? null,
31872
31880
  input.triggerType ?? "cron",
31873
31881
  input.triggerConfig ?? null,
31882
+ input.webhookToken ?? null,
31874
31883
  input.scheduledAt ?? null,
31875
31884
  input.executor ?? "claude_code",
31876
31885
  input.maxRuns ?? null,
@@ -31910,6 +31919,7 @@ function updateTask(db2, id, updates) {
31910
31919
  cronExpression: "cron_expression",
31911
31920
  triggerType: "trigger_type",
31912
31921
  triggerConfig: "trigger_config",
31922
+ webhookToken: "webhook_token",
31913
31923
  scheduledAt: "scheduled_at",
31914
31924
  executor: "executor",
31915
31925
  status: "status",
@@ -32193,6 +32203,7 @@ function mapTaskRow(row) {
32193
32203
  cronExpression: row.cron_expression,
32194
32204
  triggerType: row.trigger_type,
32195
32205
  triggerConfig: row.trigger_config,
32206
+ webhookToken: row.webhook_token,
32196
32207
  scheduledAt: row.scheduled_at,
32197
32208
  executor: row.executor,
32198
32209
  status: row.status,
@@ -32318,17 +32329,68 @@ function mapRoomRow(row) {
32318
32329
  queenQuietFrom: row.queen_quiet_from ?? null,
32319
32330
  queenQuietUntil: row.queen_quiet_until ?? null,
32320
32331
  config: config2,
32332
+ queenNickname: row.queen_nickname ?? null,
32321
32333
  chatSessionId: row.chat_session_id ?? null,
32322
32334
  referredByCode: row.referred_by_code ?? null,
32335
+ webhookToken: row.webhook_token ?? null,
32323
32336
  createdAt: row.created_at,
32324
32337
  updatedAt: row.updated_at
32325
32338
  };
32326
32339
  }
32327
- function createRoom(db2, name, goal, config2, referredByCode) {
32340
+ function createRoom(db2, name, goal, config2, referredByCode, queenNickname) {
32328
32341
  const configJson = config2 ? JSON.stringify({ ...DEFAULT_ROOM_CONFIG, ...config2 }) : JSON.stringify(DEFAULT_ROOM_CONFIG);
32329
- const result = db2.prepare("INSERT INTO rooms (name, goal, config, referred_by_code) VALUES (?, ?, ?, ?)").run(name, goal ?? null, configJson, referredByCode ?? null);
32342
+ const nickname = queenNickname ?? pickQueenNickname(db2);
32343
+ const result = db2.prepare("INSERT INTO rooms (name, goal, config, referred_by_code, queen_nickname) VALUES (?, ?, ?, ?, ?)").run(name, goal ?? null, configJson, referredByCode ?? null, nickname);
32330
32344
  return getRoom(db2, result.lastInsertRowid);
32331
32345
  }
32346
+ var QUEEN_WOMAN_NAMES = [
32347
+ "Alice",
32348
+ "Anna",
32349
+ "Belle",
32350
+ "Cara",
32351
+ "Dana",
32352
+ "Elena",
32353
+ "Fiona",
32354
+ "Grace",
32355
+ "Hana",
32356
+ "Iris",
32357
+ "Julia",
32358
+ "Kate",
32359
+ "Lena",
32360
+ "Luna",
32361
+ "Mara",
32362
+ "Maya",
32363
+ "Nina",
32364
+ "Nora",
32365
+ "Olga",
32366
+ "Petra",
32367
+ "Rose",
32368
+ "Sara",
32369
+ "Sofia",
32370
+ "Tara",
32371
+ "Uma",
32372
+ "Vera",
32373
+ "Wren",
32374
+ "Zara",
32375
+ "Zoe",
32376
+ "Ava",
32377
+ "Cleo",
32378
+ "Dara",
32379
+ "Emmy",
32380
+ "Gaia",
32381
+ "Hera",
32382
+ "Ines",
32383
+ "Jada",
32384
+ "Kara",
32385
+ "Lila",
32386
+ "Mina"
32387
+ ];
32388
+ function pickQueenNickname(db2) {
32389
+ const usedNames = db2.prepare(`SELECT queen_nickname FROM rooms WHERE queen_nickname IS NOT NULL AND queen_nickname != ''`).all().map((r) => r.queen_nickname.toLowerCase());
32390
+ const available = QUEEN_WOMAN_NAMES.filter((n) => !usedNames.includes(n.toLowerCase()));
32391
+ const pool = available.length > 0 ? available : QUEEN_WOMAN_NAMES;
32392
+ return pool[Math.floor(Math.random() * pool.length)];
32393
+ }
32332
32394
  function getRoom(db2, id) {
32333
32395
  const row = db2.prepare("SELECT * FROM rooms WHERE id = ?").get(id);
32334
32396
  return row ? mapRoomRow(row) : null;
@@ -32356,7 +32418,9 @@ function updateRoom(db2, id, updates) {
32356
32418
  queenQuietFrom: "queen_quiet_from",
32357
32419
  queenQuietUntil: "queen_quiet_until",
32358
32420
  config: "config",
32359
- referredByCode: "referred_by_code"
32421
+ referredByCode: "referred_by_code",
32422
+ queenNickname: "queen_nickname",
32423
+ webhookToken: "webhook_token"
32360
32424
  };
32361
32425
  const fields = [];
32362
32426
  const values = [];
@@ -32838,6 +32902,78 @@ function replyToRoomMessage(db2, id) {
32838
32902
  }
32839
32903
  var CYCLE_PRUNE_INTERVAL_MS = 5 * 60 * 1e3;
32840
32904
 
32905
+ // src/shared/db-migrations.ts
32906
+ function upsertSetting(database, key, value) {
32907
+ database.prepare(
32908
+ `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now','localtime'))
32909
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
32910
+ ).run(key, value);
32911
+ }
32912
+ function runMigrations(database, log = console.log) {
32913
+ database.exec(SCHEMA);
32914
+ if (!database.prepare("SELECT value FROM settings WHERE key = ?").get("keeper_referral_code")) {
32915
+ const code = (0, import_crypto.randomBytes)(6).toString("base64url").slice(0, 10);
32916
+ upsertSetting(database, "keeper_referral_code", code);
32917
+ }
32918
+ if (!database.prepare("SELECT value FROM settings WHERE key = ?").get("keeper_user_number")) {
32919
+ const num2 = String(1e4 + Math.floor(Math.random() * 9e4));
32920
+ upsertSetting(database, "keeper_user_number", num2);
32921
+ log(`Migrated: assigned keeper_user_number=${num2}`);
32922
+ }
32923
+ const hasQueenNickname = database.prepare(
32924
+ `SELECT name FROM pragma_table_info('rooms') WHERE name='queen_nickname'`
32925
+ ).get()?.name;
32926
+ if (!hasQueenNickname) {
32927
+ database.exec(`ALTER TABLE rooms ADD COLUMN queen_nickname TEXT`);
32928
+ log("Migrated: added queen_nickname column to rooms");
32929
+ }
32930
+ const roomsWithoutNickname = database.prepare(`SELECT id FROM rooms WHERE queen_nickname IS NULL OR queen_nickname = ''`).all();
32931
+ if (roomsWithoutNickname.length > 0) {
32932
+ for (const room of roomsWithoutNickname) {
32933
+ const nickname = pickQueenNickname(database);
32934
+ database.prepare(`UPDATE rooms SET queen_nickname = ? WHERE id = ?`).run(nickname, room.id);
32935
+ }
32936
+ log(`Migrated: assigned queen nicknames to ${roomsWithoutNickname.length} room(s)`);
32937
+ }
32938
+ const hasTaskWebhookToken = database.prepare(
32939
+ `SELECT name FROM pragma_table_info('tasks') WHERE name='webhook_token'`
32940
+ ).get()?.name;
32941
+ if (!hasTaskWebhookToken) {
32942
+ database.exec(`ALTER TABLE tasks ADD COLUMN webhook_token TEXT`);
32943
+ log("Migrated: added webhook_token column to tasks");
32944
+ }
32945
+ const hasTaskWebhookIndex = database.prepare(
32946
+ `SELECT name FROM sqlite_master WHERE type='index' AND name='idx_tasks_webhook_token'`
32947
+ ).get()?.name;
32948
+ if (!hasTaskWebhookIndex) {
32949
+ database.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_webhook_token ON tasks(webhook_token) WHERE webhook_token IS NOT NULL`);
32950
+ }
32951
+ const hasRoomWebhookToken = database.prepare(
32952
+ `SELECT name FROM pragma_table_info('rooms') WHERE name='webhook_token'`
32953
+ ).get()?.name;
32954
+ if (!hasRoomWebhookToken) {
32955
+ database.exec(`ALTER TABLE rooms ADD COLUMN webhook_token TEXT`);
32956
+ log("Migrated: added webhook_token column to rooms");
32957
+ }
32958
+ const hasRoomWebhookIndex = database.prepare(
32959
+ `SELECT name FROM sqlite_master WHERE type='index' AND name='idx_rooms_webhook_token'`
32960
+ ).get()?.name;
32961
+ if (!hasRoomWebhookIndex) {
32962
+ database.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_rooms_webhook_token ON rooms(webhook_token) WHERE webhook_token IS NOT NULL`);
32963
+ }
32964
+ const ollamaWorkers = database.prepare(`SELECT id FROM workers WHERE model LIKE 'ollama:%'`).all();
32965
+ if (ollamaWorkers.length > 0) {
32966
+ database.prepare(`UPDATE workers SET model = 'claude' WHERE model LIKE 'ollama:%'`).run();
32967
+ log(`Migrated: reset ${ollamaWorkers.length} ollama worker model(s) to 'claude'`);
32968
+ }
32969
+ const ollamaRooms = database.prepare(`SELECT id FROM rooms WHERE worker_model LIKE 'ollama:%'`).all();
32970
+ if (ollamaRooms.length > 0) {
32971
+ database.prepare(`UPDATE rooms SET worker_model = 'claude' WHERE worker_model LIKE 'ollama:%'`).run();
32972
+ log(`Migrated: reset ${ollamaRooms.length} room worker_model(s) to 'claude'`);
32973
+ }
32974
+ log("Database schema initialized");
32975
+ }
32976
+
32841
32977
  // src/shared/embeddings.ts
32842
32978
  var pipeline = null;
32843
32979
  var pipelineLoading = false;
@@ -33100,8 +33236,9 @@ function registerMemoryTools(server) {
33100
33236
 
33101
33237
  // src/mcp/tools/scheduler.ts
33102
33238
  var import_path4 = require("path");
33103
- var import_os6 = require("os");
33239
+ var import_os5 = require("os");
33104
33240
  var import_fs4 = require("fs");
33241
+ var import_crypto7 = require("crypto");
33105
33242
  var import_http = require("http");
33106
33243
  var import_node_cron = __toESM(require_node_cron());
33107
33244
 
@@ -33339,10 +33476,6 @@ function executeClaudeCode(prompt, options) {
33339
33476
  });
33340
33477
  }
33341
33478
 
33342
- // src/shared/agent-executor.ts
33343
- var import_child_process2 = require("child_process");
33344
- var import_os5 = require("os");
33345
-
33346
33479
  // src/shared/cloud-sync.ts
33347
33480
  var import_crypto6 = require("crypto");
33348
33481
  var import_os4 = require("os");
@@ -33369,9 +33502,6 @@ function getMachineId() {
33369
33502
  function getCloudApi() {
33370
33503
  return (process.env.QUOROOM_CLOUD_API ?? "https://quoroom.ai/api").replace(/\/$/, "");
33371
33504
  }
33372
- function getCloudMasterToken() {
33373
- return (process.env.QUOROOM_CLOUD_API_KEY ?? "").trim();
33374
- }
33375
33505
  var TOKEN_FILE_NAME = "cloud-room-tokens.json";
33376
33506
  var cachedTokens = null;
33377
33507
  function getCloudTokenFilePath() {
@@ -33407,9 +33537,8 @@ function setRoomToken(roomId, token) {
33407
33537
  }
33408
33538
  function cloudHeaders(roomId, extra = {}) {
33409
33539
  const roomToken = roomId ? getRoomToken(roomId) : void 0;
33410
- const token = roomToken || getCloudMasterToken();
33411
- if (!token) return extra;
33412
- return { ...extra, "X-Room-Token": token };
33540
+ if (!roomToken) return extra;
33541
+ return { ...extra, "X-Room-Token": roomToken };
33413
33542
  }
33414
33543
  async function ensureCloudRoomToken(data) {
33415
33544
  if (getRoomToken(data.roomId)) return true;
@@ -33676,726 +33805,7 @@ async function fetchReferredRooms(cloudRoomId) {
33676
33805
  }
33677
33806
  }
33678
33807
 
33679
- // src/shared/ollama-ensure.ts
33680
- var import_node_http = __toESM(require("node:http"));
33681
- var import_node_child_process = require("node:child_process");
33682
- var OLLAMA_INSTALL_TIMEOUT_MS = 12e4;
33683
- var OLLAMA_STARTUP_TIMEOUT_MS = 3e4;
33684
- function ollamaRequest(path, body, timeoutMs = 3e5) {
33685
- return new Promise((resolve2, reject) => {
33686
- const options = {
33687
- hostname: "127.0.0.1",
33688
- port: 11434,
33689
- path,
33690
- method: body ? "POST" : "GET",
33691
- headers: body ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } : void 0,
33692
- timeout: timeoutMs
33693
- };
33694
- const req = import_node_http.default.request(options, (res) => {
33695
- let data = "";
33696
- res.on("data", (chunk) => {
33697
- data += chunk;
33698
- });
33699
- res.on("end", () => {
33700
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
33701
- resolve2(data);
33702
- } else {
33703
- reject(new Error(`Ollama HTTP ${res.statusCode}: ${data}`));
33704
- }
33705
- });
33706
- });
33707
- req.on("error", reject);
33708
- req.on("timeout", () => {
33709
- req.destroy();
33710
- reject(new Error("Ollama request timeout"));
33711
- });
33712
- if (body) req.write(body);
33713
- req.end();
33714
- });
33715
- }
33716
- async function isOllamaAvailable() {
33717
- try {
33718
- await ollamaRequest("/api/tags", void 0, 5e3);
33719
- return true;
33720
- } catch {
33721
- return false;
33722
- }
33723
- }
33724
- async function listOllamaModels() {
33725
- try {
33726
- const response = await ollamaRequest("/api/tags", void 0, 5e3);
33727
- const parsed = JSON.parse(response);
33728
- return (parsed.models ?? []).map((m) => ({ name: m.name, size: m.size }));
33729
- } catch {
33730
- return [];
33731
- }
33732
- }
33733
- function hasOllamaBinary() {
33734
- try {
33735
- (0, import_node_child_process.execSync)("which ollama 2>/dev/null", { timeout: 3e3 });
33736
- return true;
33737
- } catch {
33738
- return false;
33739
- }
33740
- }
33741
- function installOllamaBinary() {
33742
- try {
33743
- if (process.platform === "darwin") {
33744
- (0, import_node_child_process.execSync)("brew install ollama 2>&1", { timeout: OLLAMA_INSTALL_TIMEOUT_MS });
33745
- } else {
33746
- (0, import_node_child_process.execSync)("curl -fsSL https://ollama.com/install.sh | sh 2>&1", { timeout: OLLAMA_INSTALL_TIMEOUT_MS });
33747
- }
33748
- return true;
33749
- } catch {
33750
- return false;
33751
- }
33752
- }
33753
- function startOllamaServe() {
33754
- try {
33755
- const child = (0, import_node_child_process.spawn)("ollama", ["serve"], { detached: true, stdio: "ignore" });
33756
- child.on("error", () => {
33757
- });
33758
- child.unref();
33759
- return true;
33760
- } catch {
33761
- return false;
33762
- }
33763
- }
33764
- async function waitForOllamaAvailable(timeoutMs = OLLAMA_STARTUP_TIMEOUT_MS) {
33765
- const startedAt = Date.now();
33766
- while (Date.now() - startedAt < timeoutMs) {
33767
- if (await isOllamaAvailable()) return true;
33768
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
33769
- }
33770
- return false;
33771
- }
33772
- async function ensureOllamaRunning() {
33773
- const already = await isOllamaAvailable();
33774
- if (already) return { available: true, status: "running" };
33775
- if (!hasOllamaBinary() && !installOllamaBinary()) {
33776
- return { available: false, status: "install_failed" };
33777
- }
33778
- if (!startOllamaServe()) {
33779
- return { available: false, status: "start_failed" };
33780
- }
33781
- const available = await waitForOllamaAvailable();
33782
- return { available, status: available ? "running" : "start_failed" };
33783
- }
33784
- function isModelInstalled(models, requested) {
33785
- const requestedLower = requested.toLowerCase();
33786
- for (const model of models) {
33787
- const installedName = model.name.toLowerCase();
33788
- if (installedName === requestedLower) return true;
33789
- if (!requestedLower.includes(":") && installedName === `${requestedLower}:latest`) return true;
33790
- if (requestedLower.endsWith(":latest") && installedName === requestedLower.slice(0, -7)) return true;
33791
- }
33792
- return false;
33793
- }
33794
- async function pullOllamaModel(model) {
33795
- return await new Promise((resolve2) => {
33796
- let stdout = "";
33797
- let stderr = "";
33798
- let settled = false;
33799
- let proc;
33800
- try {
33801
- proc = (0, import_node_child_process.spawn)("ollama", ["pull", model], {
33802
- stdio: ["ignore", "pipe", "pipe"]
33803
- });
33804
- } catch (err) {
33805
- const message = err instanceof Error ? err.message : String(err);
33806
- resolve2({ ok: false, error: `Failed to start ollama pull: ${message}` });
33807
- return;
33808
- }
33809
- const finish = (result) => {
33810
- if (settled) return;
33811
- settled = true;
33812
- resolve2(result);
33813
- };
33814
- const timer = setTimeout(() => {
33815
- proc.kill("SIGTERM");
33816
- finish({ ok: false, error: `Timed out while pulling model ${model}` });
33817
- }, 15 * 60 * 1e3);
33818
- proc.stdout?.on("data", (chunk) => {
33819
- stdout += String(chunk);
33820
- });
33821
- proc.stderr?.on("data", (chunk) => {
33822
- stderr += String(chunk);
33823
- });
33824
- proc.on("error", (err) => {
33825
- clearTimeout(timer);
33826
- const message = err instanceof Error ? err.message : String(err);
33827
- finish({ ok: false, error: `ollama pull failed: ${message}` });
33828
- });
33829
- proc.on("close", (code) => {
33830
- clearTimeout(timer);
33831
- if (code === 0) {
33832
- finish({ ok: true });
33833
- return;
33834
- }
33835
- const details = `${stderr}
33836
- ${stdout}`.trim().split("\n").slice(-3).join("\n");
33837
- finish({ ok: false, error: details || `ollama pull exited with code ${code ?? -1}` });
33838
- });
33839
- });
33840
- }
33841
- async function ensureOllamaModel(modelName) {
33842
- const running = await ensureOllamaRunning();
33843
- if (!running.available) {
33844
- throw new Error(`Ollama unavailable (${running.status})`);
33845
- }
33846
- const installed = await listOllamaModels();
33847
- if (isModelInstalled(installed, modelName)) return;
33848
- const pulled = await pullOllamaModel(modelName);
33849
- if (!pulled.ok) {
33850
- throw new Error(`Failed to pull model ${modelName}: ${pulled.ok === false ? pulled.error : "unknown"}`);
33851
- }
33852
- }
33853
-
33854
33808
  // src/shared/agent-executor.ts
33855
- var DEFAULT_HTTP_TIMEOUT_MS = 6e4;
33856
- async function executeAgent(options) {
33857
- const model = options.model.trim();
33858
- if (model.startsWith("ollama:")) {
33859
- if (options.toolDefs && options.toolDefs.length > 0 && options.onToolCall) {
33860
- return executeOllamaWithTools(options);
33861
- }
33862
- return executeOllama(options);
33863
- }
33864
- if (model === "codex" || model.startsWith("codex:")) {
33865
- return executeCodex(options);
33866
- }
33867
- if (model === "openai" || model.startsWith("openai:")) {
33868
- if (options.toolDefs && options.toolDefs.length > 0 && options.onToolCall) {
33869
- return executeOpenAiWithTools(options);
33870
- }
33871
- return executeOpenAiApi(options);
33872
- }
33873
- if (model === "anthropic" || model.startsWith("anthropic:") || model.startsWith("claude-api:")) {
33874
- if (options.toolDefs && options.toolDefs.length > 0 && options.onToolCall) {
33875
- return executeAnthropicWithTools(options);
33876
- }
33877
- return executeAnthropicApi(options);
33878
- }
33879
- return executeClaude(options);
33880
- }
33881
- async function executeClaude(options) {
33882
- const execOpts = {
33883
- timeoutMs: options.timeoutMs,
33884
- maxTurns: options.maxTurns,
33885
- allowedTools: options.allowedTools,
33886
- disallowedTools: options.disallowedTools,
33887
- onProgress: options.onProgress,
33888
- onConsoleLog: options.onConsoleLog,
33889
- resumeSessionId: options.resumeSessionId,
33890
- systemPrompt: options.systemPrompt,
33891
- model: options.model === "claude" ? void 0 : options.model
33892
- };
33893
- const result = await executeClaudeCode(options.prompt, execOpts);
33894
- return {
33895
- output: result.stdout,
33896
- exitCode: result.exitCode,
33897
- durationMs: result.durationMs,
33898
- sessionId: result.sessionId,
33899
- timedOut: result.timedOut
33900
- };
33901
- }
33902
- async function executeCodex(options) {
33903
- return new Promise((resolve2) => {
33904
- const startTime = Date.now();
33905
- let stdout = "";
33906
- let stderr = "";
33907
- let timedOut = false;
33908
- let settled = false;
33909
- let sessionId = options.resumeSessionId ?? null;
33910
- let outputParts = [];
33911
- let buffer2 = "";
33912
- const modelName = parseModelSuffix(options.model, "codex");
33913
- const prompt = buildPrompt(options.systemPrompt, options.prompt);
33914
- const args = options.resumeSessionId ? ["exec", "resume", "--json", "--skip-git-repo-check", options.resumeSessionId, prompt] : ["exec", "--json", "--skip-git-repo-check", prompt];
33915
- if (modelName) {
33916
- args.splice(2, 0, "--model", modelName);
33917
- }
33918
- let proc;
33919
- try {
33920
- proc = (0, import_child_process2.spawn)("codex", args, {
33921
- cwd: (0, import_os5.homedir)(),
33922
- env: process.env,
33923
- stdio: ["ignore", "pipe", "pipe"],
33924
- windowsHide: true
33925
- });
33926
- } catch (err) {
33927
- resolve2({
33928
- output: `Error: Failed to spawn codex CLI: ${err instanceof Error ? err.message : String(err)}`,
33929
- exitCode: 1,
33930
- durationMs: Date.now() - startTime,
33931
- sessionId: null,
33932
- timedOut: false
33933
- });
33934
- return;
33935
- }
33936
- if (!proc.stdout || !proc.stderr) {
33937
- resolve2({
33938
- output: "Error: Failed to create stdio pipes for codex CLI",
33939
- exitCode: 1,
33940
- durationMs: Date.now() - startTime,
33941
- sessionId: null,
33942
- timedOut: false
33943
- });
33944
- try {
33945
- proc.kill();
33946
- } catch {
33947
- }
33948
- return;
33949
- }
33950
- proc.stdout.on("data", (data) => {
33951
- const chunk = data.toString();
33952
- stdout += chunk;
33953
- buffer2 += chunk;
33954
- const lines = buffer2.split("\n");
33955
- buffer2 = lines.pop() ?? "";
33956
- for (const line of lines) {
33957
- parseCodexEventLine(line, (nextSessionId, textChunk) => {
33958
- if (nextSessionId) sessionId = nextSessionId;
33959
- if (textChunk) outputParts.push(textChunk);
33960
- });
33961
- }
33962
- });
33963
- proc.stderr.on("data", (data) => {
33964
- stderr += data.toString();
33965
- });
33966
- const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
33967
- const timer = setTimeout(() => {
33968
- if (settled) return;
33969
- timedOut = true;
33970
- proc.kill("SIGTERM");
33971
- setTimeout(() => proc.kill("SIGKILL"), 5e3);
33972
- }, timeoutMs);
33973
- proc.on("close", (code) => {
33974
- if (settled) return;
33975
- settled = true;
33976
- clearTimeout(timer);
33977
- if (buffer2.trim()) {
33978
- parseCodexEventLine(buffer2.trim(), (nextSessionId, textChunk) => {
33979
- if (nextSessionId) sessionId = nextSessionId;
33980
- if (textChunk) outputParts.push(textChunk);
33981
- });
33982
- }
33983
- const output = outputParts.join("\n\n").trim() || stderr.trim() || stdout.trim() || "";
33984
- resolve2({
33985
- output,
33986
- exitCode: code ?? (timedOut ? 124 : 1),
33987
- durationMs: Date.now() - startTime,
33988
- sessionId,
33989
- timedOut
33990
- });
33991
- });
33992
- proc.on("error", (err) => {
33993
- if (settled) return;
33994
- settled = true;
33995
- clearTimeout(timer);
33996
- resolve2({
33997
- output: `Error: ${err.message}`,
33998
- exitCode: 1,
33999
- durationMs: Date.now() - startTime,
34000
- sessionId,
34001
- timedOut: false
34002
- });
34003
- });
34004
- });
34005
- }
34006
- async function executeOllamaWithTools(options) {
34007
- const modelName = options.model.replace(/^ollama:/, "");
34008
- const startTime = Date.now();
34009
- try {
34010
- await ensureOllamaModel(modelName);
34011
- } catch (err) {
34012
- return {
34013
- output: `Error: ${err instanceof Error ? err.message : String(err)}`,
34014
- exitCode: 1,
34015
- durationMs: Date.now() - startTime,
34016
- sessionId: null,
34017
- timedOut: false
34018
- };
34019
- }
34020
- const messages = [];
34021
- if (options.systemPrompt) {
34022
- messages.push({ role: "system", content: options.systemPrompt });
34023
- }
34024
- messages.push({ role: "user", content: options.prompt });
34025
- const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
34026
- const maxTurns = options.maxTurns ?? 10;
34027
- let finalOutput = "";
34028
- for (let turn = 0; turn < maxTurns; turn++) {
34029
- const body = JSON.stringify({
34030
- model: modelName,
34031
- messages,
34032
- tools: options.toolDefs,
34033
- stream: false
34034
- });
34035
- let raw;
34036
- try {
34037
- raw = await ollamaRequest("/api/chat", body, timeoutMs);
34038
- } catch (err) {
34039
- const error2 = err;
34040
- const isTimeout = error2.message.includes("timeout") || error2.message.includes("aborted");
34041
- return {
34042
- output: `Error: ${error2.message}`,
34043
- exitCode: 1,
34044
- durationMs: Date.now() - startTime,
34045
- sessionId: null,
34046
- timedOut: isTimeout
34047
- };
34048
- }
34049
- let parsed;
34050
- try {
34051
- parsed = JSON.parse(raw);
34052
- } catch {
34053
- return {
34054
- output: raw,
34055
- exitCode: 0,
34056
- durationMs: Date.now() - startTime,
34057
- sessionId: null,
34058
- timedOut: false
34059
- };
34060
- }
34061
- const msg = parsed.message;
34062
- const toolCalls = msg.tool_calls ?? [];
34063
- if (toolCalls.length === 0) {
34064
- finalOutput = msg.content ?? "";
34065
- break;
34066
- }
34067
- messages.push({ role: "assistant", content: msg.content ?? "", tool_calls: toolCalls });
34068
- for (const tc of toolCalls) {
34069
- const name = tc.function.name;
34070
- const rawArgs = tc.function.arguments;
34071
- const args = typeof rawArgs === "string" ? (() => {
34072
- try {
34073
- return JSON.parse(rawArgs);
34074
- } catch {
34075
- return {};
34076
- }
34077
- })() : rawArgs;
34078
- let toolResult = `Tool ${name} unavailable`;
34079
- if (options.onToolCall) {
34080
- try {
34081
- toolResult = await options.onToolCall(name, args);
34082
- } catch (err) {
34083
- toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
34084
- }
34085
- }
34086
- messages.push({ role: "tool", content: toolResult });
34087
- }
34088
- }
34089
- return {
34090
- output: finalOutput || "Actions completed.",
34091
- exitCode: 0,
34092
- durationMs: Date.now() - startTime,
34093
- sessionId: null,
34094
- timedOut: false
34095
- };
34096
- }
34097
- async function executeOpenAiWithTools(options) {
34098
- const apiKey = options.apiKey?.trim() || (process.env.OPENAI_API_KEY || "").trim();
34099
- if (!apiKey) return immediateError("Missing OpenAI API key.");
34100
- const modelName = parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
34101
- const startTime = Date.now();
34102
- const maxTurns = options.maxTurns ?? 10;
34103
- const messages = [];
34104
- if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
34105
- messages.push({ role: "user", content: options.prompt });
34106
- let finalOutput = "";
34107
- for (let turn = 0; turn < maxTurns; turn++) {
34108
- const controller = new AbortController();
34109
- const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
34110
- const timer = setTimeout(() => controller.abort(), timeoutMs);
34111
- let json;
34112
- try {
34113
- const response = await fetch("https://api.openai.com/v1/chat/completions", {
34114
- method: "POST",
34115
- headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
34116
- body: JSON.stringify({ model: modelName, messages, tools: options.toolDefs }),
34117
- signal: controller.signal
34118
- });
34119
- json = await response.json();
34120
- if (!response.ok) {
34121
- return { output: `OpenAI API ${response.status}: ${extractApiError(json)}`, exitCode: 1, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
34122
- }
34123
- } catch (err) {
34124
- const msg2 = err instanceof Error ? err.message : String(err);
34125
- const timedOut = msg2.toLowerCase().includes("aborted") || msg2.toLowerCase().includes("timeout");
34126
- return { output: `Error: ${msg2}`, exitCode: 1, durationMs: Date.now() - startTime, sessionId: null, timedOut };
34127
- } finally {
34128
- clearTimeout(timer);
34129
- }
34130
- const choices = json.choices;
34131
- const choice = choices?.[0];
34132
- const msg = choice?.message;
34133
- if (!msg) break;
34134
- const toolCalls = msg.tool_calls ?? [];
34135
- if (toolCalls.length === 0) {
34136
- finalOutput = (typeof msg.content === "string" ? msg.content : "") ?? "";
34137
- break;
34138
- }
34139
- messages.push({ role: "assistant", content: msg.content ?? null, tool_calls: toolCalls });
34140
- for (const tc of toolCalls) {
34141
- const name = tc.function.name;
34142
- let args = {};
34143
- try {
34144
- args = JSON.parse(tc.function.arguments);
34145
- } catch {
34146
- }
34147
- let toolResult = `Tool ${name} unavailable`;
34148
- if (options.onToolCall) {
34149
- try {
34150
- toolResult = await options.onToolCall(name, args);
34151
- } catch (err) {
34152
- toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
34153
- }
34154
- }
34155
- messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
34156
- }
34157
- }
34158
- return { output: finalOutput || "Actions completed.", exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
34159
- }
34160
- function ollamaToolDefsToAnthropic(defs) {
34161
- return defs.map((d) => ({
34162
- name: d.function.name,
34163
- description: d.function.description,
34164
- input_schema: d.function.parameters
34165
- }));
34166
- }
34167
- async function executeAnthropicWithTools(options) {
34168
- const apiKey = options.apiKey?.trim() || (process.env.ANTHROPIC_API_KEY || "").trim();
34169
- if (!apiKey) return immediateError("Missing Anthropic API key.");
34170
- const modelName = parseAnthropicModel(options.model);
34171
- const startTime = Date.now();
34172
- const maxTurns = options.maxTurns ?? 10;
34173
- const anthropicTools = options.toolDefs ? ollamaToolDefsToAnthropic(options.toolDefs) : [];
34174
- const messages = [{ role: "user", content: options.prompt }];
34175
- let finalOutput = "";
34176
- for (let turn = 0; turn < maxTurns; turn++) {
34177
- const controller = new AbortController();
34178
- const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
34179
- const timer = setTimeout(() => controller.abort(), timeoutMs);
34180
- let json;
34181
- try {
34182
- const response = await fetch("https://api.anthropic.com/v1/messages", {
34183
- method: "POST",
34184
- headers: {
34185
- "x-api-key": apiKey,
34186
- "anthropic-version": "2023-06-01",
34187
- "content-type": "application/json"
34188
- },
34189
- body: JSON.stringify({
34190
- model: modelName,
34191
- max_tokens: 4096,
34192
- system: options.systemPrompt,
34193
- tools: anthropicTools,
34194
- messages
34195
- }),
34196
- signal: controller.signal
34197
- });
34198
- json = await response.json();
34199
- if (!response.ok) {
34200
- return { output: `Anthropic API ${response.status}: ${extractApiError(json)}`, exitCode: 1, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
34201
- }
34202
- } catch (err) {
34203
- const msg = err instanceof Error ? err.message : String(err);
34204
- const timedOut = msg.toLowerCase().includes("aborted") || msg.toLowerCase().includes("timeout");
34205
- return { output: `Error: ${msg}`, exitCode: 1, durationMs: Date.now() - startTime, sessionId: null, timedOut };
34206
- } finally {
34207
- clearTimeout(timer);
34208
- }
34209
- const stopReason = json.stop_reason;
34210
- const content = json.content;
34211
- const toolUseBlocks = (content ?? []).filter((b) => b.type === "tool_use");
34212
- const textBlocks = (content ?? []).filter((b) => b.type === "text");
34213
- if (toolUseBlocks.length === 0 || stopReason !== "tool_use") {
34214
- finalOutput = textBlocks.map((b) => b.text ?? "").join("\n").trim();
34215
- break;
34216
- }
34217
- messages.push({ role: "assistant", content: content ?? [] });
34218
- const resultBlocks = [];
34219
- for (const block of toolUseBlocks) {
34220
- const name = block.name ?? "";
34221
- const args = block.input ?? {};
34222
- let toolResult = `Tool ${name} unavailable`;
34223
- if (options.onToolCall) {
34224
- try {
34225
- toolResult = await options.onToolCall(name, args);
34226
- } catch (err) {
34227
- toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
34228
- }
34229
- }
34230
- resultBlocks.push({ type: "tool_result", id: block.id, content: toolResult });
34231
- }
34232
- messages.push({ role: "user", content: resultBlocks });
34233
- }
34234
- return { output: finalOutput || "Actions completed.", exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
34235
- }
34236
- async function executeOpenAiApi(options) {
34237
- const apiKey = options.apiKey?.trim() || (process.env.OPENAI_API_KEY || "").trim();
34238
- if (!apiKey) {
34239
- return immediateError('Missing OpenAI API key. Set room credential "openai_api_key" or OPENAI_API_KEY.');
34240
- }
34241
- const modelName = parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
34242
- const messages = [];
34243
- if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
34244
- messages.push({ role: "user", content: options.prompt });
34245
- const startTime = Date.now();
34246
- const controller = new AbortController();
34247
- const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
34248
- const timer = setTimeout(() => controller.abort(), timeoutMs);
34249
- try {
34250
- const response = await fetch("https://api.openai.com/v1/chat/completions", {
34251
- method: "POST",
34252
- headers: {
34253
- "Authorization": `Bearer ${apiKey}`,
34254
- "Content-Type": "application/json"
34255
- },
34256
- body: JSON.stringify({
34257
- model: modelName,
34258
- messages
34259
- }),
34260
- signal: controller.signal
34261
- });
34262
- const json = await response.json();
34263
- if (!response.ok) {
34264
- return {
34265
- output: `OpenAI API ${response.status}: ${extractApiError(json)}`,
34266
- exitCode: 1,
34267
- durationMs: Date.now() - startTime,
34268
- sessionId: null,
34269
- timedOut: false
34270
- };
34271
- }
34272
- const output = extractOpenAiText(json);
34273
- return {
34274
- output,
34275
- exitCode: 0,
34276
- durationMs: Date.now() - startTime,
34277
- sessionId: null,
34278
- timedOut: false
34279
- };
34280
- } catch (err) {
34281
- const message = err instanceof Error ? err.message : String(err);
34282
- const timedOut = message.toLowerCase().includes("aborted") || message.toLowerCase().includes("timeout");
34283
- return {
34284
- output: `Error: ${message}`,
34285
- exitCode: 1,
34286
- durationMs: Date.now() - startTime,
34287
- sessionId: null,
34288
- timedOut
34289
- };
34290
- } finally {
34291
- clearTimeout(timer);
34292
- }
34293
- }
34294
- async function executeAnthropicApi(options) {
34295
- const apiKey = options.apiKey?.trim() || (process.env.ANTHROPIC_API_KEY || "").trim();
34296
- if (!apiKey) {
34297
- return immediateError('Missing Anthropic API key. Set room credential "anthropic_api_key" or ANTHROPIC_API_KEY.');
34298
- }
34299
- const modelName = parseAnthropicModel(options.model);
34300
- const startTime = Date.now();
34301
- const controller = new AbortController();
34302
- const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
34303
- const timer = setTimeout(() => controller.abort(), timeoutMs);
34304
- try {
34305
- const response = await fetch("https://api.anthropic.com/v1/messages", {
34306
- method: "POST",
34307
- headers: {
34308
- "x-api-key": apiKey,
34309
- "anthropic-version": "2023-06-01",
34310
- "content-type": "application/json"
34311
- },
34312
- body: JSON.stringify({
34313
- model: modelName,
34314
- max_tokens: 2048,
34315
- system: options.systemPrompt,
34316
- messages: [{ role: "user", content: options.prompt }]
34317
- }),
34318
- signal: controller.signal
34319
- });
34320
- const json = await response.json();
34321
- if (!response.ok) {
34322
- return {
34323
- output: `Anthropic API ${response.status}: ${extractApiError(json)}`,
34324
- exitCode: 1,
34325
- durationMs: Date.now() - startTime,
34326
- sessionId: null,
34327
- timedOut: false
34328
- };
34329
- }
34330
- const output = extractAnthropicText(json);
34331
- return {
34332
- output,
34333
- exitCode: 0,
34334
- durationMs: Date.now() - startTime,
34335
- sessionId: null,
34336
- timedOut: false
34337
- };
34338
- } catch (err) {
34339
- const message = err instanceof Error ? err.message : String(err);
34340
- const timedOut = message.toLowerCase().includes("aborted") || message.toLowerCase().includes("timeout");
34341
- return {
34342
- output: `Error: ${message}`,
34343
- exitCode: 1,
34344
- durationMs: Date.now() - startTime,
34345
- sessionId: null,
34346
- timedOut
34347
- };
34348
- } finally {
34349
- clearTimeout(timer);
34350
- }
34351
- }
34352
- async function executeOllama(options) {
34353
- const modelName = options.model.replace(/^ollama:/, "");
34354
- const startTime = Date.now();
34355
- try {
34356
- await ensureOllamaModel(modelName);
34357
- } catch (err) {
34358
- return {
34359
- output: `Error: ${err instanceof Error ? err.message : String(err)}`,
34360
- exitCode: 1,
34361
- durationMs: Date.now() - startTime,
34362
- sessionId: null,
34363
- timedOut: false
34364
- };
34365
- }
34366
- const messages = [];
34367
- if (options.systemPrompt) {
34368
- messages.push({ role: "system", content: options.systemPrompt });
34369
- }
34370
- messages.push({ role: "user", content: options.prompt });
34371
- const body = JSON.stringify({
34372
- model: modelName,
34373
- messages,
34374
- stream: false
34375
- });
34376
- try {
34377
- const response = await ollamaRequest("/api/chat", body, options.timeoutMs ?? 5 * 60 * 1e3);
34378
- const parsed = JSON.parse(response);
34379
- const output = parsed?.message?.content ?? "";
34380
- return {
34381
- output,
34382
- exitCode: 0,
34383
- durationMs: Date.now() - startTime,
34384
- sessionId: null,
34385
- timedOut: false
34386
- };
34387
- } catch (err) {
34388
- const error2 = err;
34389
- const isTimeout = error2.message.includes("timeout") || error2.message.includes("aborted");
34390
- return {
34391
- output: `Error: ${error2.message}`,
34392
- exitCode: 1,
34393
- durationMs: Date.now() - startTime,
34394
- sessionId: null,
34395
- timedOut: isTimeout
34396
- };
34397
- }
34398
- }
34399
33809
  function parseModelSuffix(model, prefix) {
34400
33810
  const trimmed = model.trim();
34401
33811
  if (trimmed === prefix) return "";
@@ -34412,29 +33822,6 @@ function parseAnthropicModel(model) {
34412
33822
  if (claudeApiModel) return claudeApiModel;
34413
33823
  return normalized;
34414
33824
  }
34415
- function parseCodexEventLine(line, onEvent) {
34416
- const trimmed = line.trim();
34417
- if (!trimmed) return;
34418
- let parsed;
34419
- try {
34420
- parsed = JSON.parse(trimmed);
34421
- } catch {
34422
- return;
34423
- }
34424
- const type = parsed.type;
34425
- if (type === "thread.started" && typeof parsed.thread_id === "string") {
34426
- onEvent(parsed.thread_id, null);
34427
- return;
34428
- }
34429
- if (type === "item.completed") {
34430
- const item = parsed.item;
34431
- if (!item) return;
34432
- const itemType = item.type;
34433
- if (itemType === "agent_message" && typeof item.text === "string") {
34434
- onEvent(null, item.text);
34435
- }
34436
- }
34437
- }
34438
33825
  function extractOpenAiText(json) {
34439
33826
  const choices = json.choices;
34440
33827
  if (Array.isArray(choices) && choices.length > 0) {
@@ -34464,20 +33851,6 @@ function extractAnthropicText(json) {
34464
33851
  }
34465
33852
  return JSON.stringify(json);
34466
33853
  }
34467
- function extractApiError(json) {
34468
- const error2 = json.error;
34469
- if (!error2) return JSON.stringify(json);
34470
- if (typeof error2.message === "string") return error2.message;
34471
- return JSON.stringify(error2);
34472
- }
34473
- function buildPrompt(systemPrompt, prompt) {
34474
- if (!systemPrompt) return prompt;
34475
- return `System instructions:
34476
- ${systemPrompt}
34477
-
34478
- User request:
34479
- ${prompt}`;
34480
- }
34481
33854
  function immediateError(message) {
34482
33855
  return {
34483
33856
  output: `Error: ${message}`,
@@ -34487,59 +33860,6 @@ function immediateError(message) {
34487
33860
  timedOut: false
34488
33861
  };
34489
33862
  }
34490
- async function executeOllamaOnStation(cloudRoomId, stationId, options) {
34491
- const modelName = options.model.replace(/^ollama:/, "");
34492
- const startTime = Date.now();
34493
- const messages = [];
34494
- if (options.systemPrompt) {
34495
- messages.push({ role: "system", content: options.systemPrompt });
34496
- }
34497
- messages.push({ role: "user", content: options.prompt });
34498
- const payload = JSON.stringify({
34499
- model: modelName,
34500
- messages,
34501
- stream: false
34502
- });
34503
- const b64 = Buffer.from(payload).toString("base64");
34504
- const command = `echo '${b64}' | base64 -d | curl -s --max-time 300 http://localhost:11434/api/chat -d @-`;
34505
- const result = await execOnCloudStation(cloudRoomId, stationId, command, 36e4);
34506
- if (!result) {
34507
- return {
34508
- output: "Error: station execution failed (station unreachable or Ollama not running)",
34509
- exitCode: 1,
34510
- durationMs: Date.now() - startTime,
34511
- sessionId: null,
34512
- timedOut: false
34513
- };
34514
- }
34515
- if (result.exitCode !== 0) {
34516
- return {
34517
- output: result.stderr || result.stdout || `Station exec failed with exit code ${result.exitCode}`,
34518
- exitCode: result.exitCode,
34519
- durationMs: Date.now() - startTime,
34520
- sessionId: null,
34521
- timedOut: false
34522
- };
34523
- }
34524
- try {
34525
- const parsed = JSON.parse(result.stdout);
34526
- return {
34527
- output: parsed?.message?.content ?? "",
34528
- exitCode: 0,
34529
- durationMs: Date.now() - startTime,
34530
- sessionId: null,
34531
- timedOut: false
34532
- };
34533
- } catch {
34534
- return {
34535
- output: result.stdout || "(no output from Ollama)",
34536
- exitCode: 1,
34537
- durationMs: Date.now() - startTime,
34538
- sessionId: null,
34539
- timedOut: false
34540
- };
34541
- }
34542
- }
34543
33863
  async function executeApiOnStation(cloudRoomId, stationId, options) {
34544
33864
  const startTime = Date.now();
34545
33865
  const apiKey = options.apiKey;
@@ -34613,7 +33933,6 @@ function normalizeModel(model) {
34613
33933
  }
34614
33934
  function getModelProvider(model) {
34615
33935
  const normalized = normalizeModel(model);
34616
- if (normalized.startsWith("ollama:")) return "ollama";
34617
33936
  if (normalized === "codex" || normalized.startsWith("codex:")) return "codex_subscription";
34618
33937
  if (normalized === "openai" || normalized.startsWith("openai:")) return "openai_api";
34619
33938
  if (normalized === "anthropic" || normalized.startsWith("anthropic:") || normalized.startsWith("claude-api:")) {
@@ -34984,20 +34303,13 @@ async function executeTask(taskId, options) {
34984
34303
  } catch (err) {
34985
34304
  console.warn("Non-fatal: worker resolution failed:", err);
34986
34305
  }
34987
- const isStationModel = model?.startsWith("ollama:") || model?.startsWith("openai:") || model?.startsWith("anthropic:") || model?.startsWith("claude-api:");
34306
+ const isStationModel = model?.startsWith("openai:") || model?.startsWith("anthropic:") || model?.startsWith("claude-api:");
34988
34307
  if (isStationModel && task.roomId) {
34989
34308
  runningTasks.add(taskId);
34990
34309
  try {
34991
34310
  const cloudRoomId = getRoomCloudId(task.roomId);
34992
34311
  const stations = await listCloudStations(cloudRoomId);
34993
34312
  const activeStations = stations.filter((s) => s.status === "active");
34994
- if (activeStations.length === 0 && model?.startsWith("ollama:")) {
34995
- const run2 = createTaskRun(db2, taskId);
34996
- const errorMsg = "No active station available. Ollama workers require a station. Rent one with quoroom_station_create (minimum tier: small).";
34997
- completeTaskRun(db2, run2.id, "", void 0, errorMsg);
34998
- onFailed?.(task, errorMsg);
34999
- return { success: false, output: "", errorMessage: errorMsg, durationMs: Date.now() - startTime };
35000
- }
35001
34313
  if (activeStations.length > 0) {
35002
34314
  const run2 = createTaskRun(db2, taskId);
35003
34315
  try {
@@ -35029,25 +34341,15 @@ ${augmentedPrompt}`;
35029
34341
  }
35030
34342
  const timeoutMs = task.timeoutMinutes != null ? task.timeoutMinutes * 60 * 1e3 : void 0;
35031
34343
  const stationModel = model;
35032
- let agentResult;
35033
- if (stationModel.startsWith("ollama:")) {
35034
- agentResult = await executeOllamaOnStation(cloudRoomId, station.id, {
35035
- model: stationModel,
35036
- prompt: augmentedPrompt,
35037
- systemPrompt,
35038
- timeoutMs
35039
- });
35040
- } else {
35041
- const apiKey = resolveApiKeyForModel(db2, task.roomId, stationModel);
35042
- agentResult = await executeApiOnStation(cloudRoomId, station.id, {
35043
- model: stationModel,
35044
- prompt: augmentedPrompt,
35045
- systemPrompt,
35046
- timeoutMs,
35047
- apiKey
35048
- });
35049
- }
35050
- const result = ollamaResultToExecutionResult(agentResult);
34344
+ const apiKey = resolveApiKeyForModel(db2, task.roomId, stationModel);
34345
+ const agentResult = await executeApiOnStation(cloudRoomId, station.id, {
34346
+ model: stationModel,
34347
+ prompt: augmentedPrompt,
34348
+ systemPrompt,
34349
+ timeoutMs,
34350
+ apiKey
34351
+ });
34352
+ const result = agentResultToExecutionResult(agentResult);
35051
34353
  return finishRun(db2, run2.id, taskId, task, result, resultsDir, onComplete, onFailed);
35052
34354
  } catch (err) {
35053
34355
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -35121,17 +34423,6 @@ ${augmentedPrompt}`;
35121
34423
  const disallowedTools = task.disallowedTools ?? void 0;
35122
34424
  const consoleLog = createConsoleLogBuffer(db2, run.id, onConsoleLogEntry);
35123
34425
  let lastProgressUpdate = 0;
35124
- if (model?.startsWith("ollama:")) {
35125
- const agentResult = await executeAgent({
35126
- model,
35127
- prompt: augmentedPrompt,
35128
- systemPrompt,
35129
- timeoutMs
35130
- });
35131
- consoleLog.flush();
35132
- const result2 = ollamaResultToExecutionResult(agentResult);
35133
- return finishRun(db2, run.id, taskId, task, result2, resultsDir, onComplete, onFailed);
35134
- }
35135
34426
  const execOptions = {
35136
34427
  systemPrompt,
35137
34428
  model,
@@ -35215,7 +34506,7 @@ ${retryPrompt}`;
35215
34506
  releaseSlot();
35216
34507
  }
35217
34508
  }
35218
- function ollamaResultToExecutionResult(result) {
34509
+ function agentResultToExecutionResult(result) {
35219
34510
  return {
35220
34511
  stdout: result.output,
35221
34512
  stderr: "",
@@ -35266,6 +34557,15 @@ function finishRun(db2, runId, taskId, task, result, resultsDir, onComplete, onF
35266
34557
  } catch (err) {
35267
34558
  console.warn("Non-fatal: memory storage failed:", err);
35268
34559
  }
34560
+ const fullError = (output + " " + errorMsg).toLowerCase();
34561
+ const isTerminalError = !result.timedOut && (fullError.includes("failed to spawn") || fullError.includes("enoent") || fullError.includes("missing openai api key") || fullError.includes("missing anthropic api key") || fullError.includes("missing api key"));
34562
+ if (isTerminalError) {
34563
+ try {
34564
+ updateTask(db2, taskId, { status: "paused" });
34565
+ console.log(`Task ${taskId} auto-paused: terminal error (won't retry): ${errorMsg.slice(0, 100)}`);
34566
+ } catch {
34567
+ }
34568
+ }
35269
34569
  onFailed?.(task, errorMsg);
35270
34570
  return { success: false, output, errorMessage: errorMsg, durationMs: result.durationMs, resultFilePath };
35271
34571
  }
@@ -35293,6 +34593,19 @@ ${output}
35293
34593
  }
35294
34594
 
35295
34595
  // src/mcp/tools/scheduler.ts
34596
+ function getServerPort() {
34597
+ try {
34598
+ const dbPath = process.env.QUOROOM_DB_PATH;
34599
+ const dataDir = process.env.QUOROOM_DATA_DIR || (dbPath ? (0, import_path4.dirname)(dbPath) : (0, import_path4.join)((0, import_os5.homedir)(), `.${APP_NAME.toLowerCase()}`));
34600
+ const portFile = (0, import_path4.join)(dataDir, "api.port");
34601
+ if ((0, import_fs4.existsSync)(portFile)) {
34602
+ const port = parseInt((0, import_fs4.readFileSync)(portFile, "utf-8").trim(), 10);
34603
+ return Number.isFinite(port) && port > 0 ? port : null;
34604
+ }
34605
+ } catch {
34606
+ }
34607
+ return null;
34608
+ }
35296
34609
  function generateTaskName(prompt) {
35297
34610
  const cleaned = prompt.replace(/^(please |can you |i want you to |i need you to )/i, "").trim();
35298
34611
  const firstSentence = cleaned.split(/[.\n]/)[0].trim();
@@ -35304,7 +34617,7 @@ function registerSchedulerTools(server) {
35304
34617
  "quoroom_schedule",
35305
34618
  {
35306
34619
  title: "Schedule Task",
35307
- description: "Create a task \u2014 recurring (cron), one-time (specific datetime), or on-demand (manual trigger). Provide cronExpression for recurring, scheduledAt for one-time, or neither for on-demand. RESPONSE STYLE: After calling this tool, confirm to the user in 1 short sentence. Do NOT add notes, tips, caveats, or advice. Do NOT mention task IDs, cron syntax, session continuity, workers, timeouts, Electron, or internal tool names.",
34620
+ description: 'Create a task \u2014 recurring (cron), one-time (specific datetime), on-demand (manual trigger), or webhook-triggered. Provide cronExpression for recurring, scheduledAt for one-time, triggerType="webhook" for HTTP-triggered tasks, or neither for on-demand. RESPONSE STYLE: After calling this tool, confirm to the user in 1 short sentence. Do NOT add notes, tips, caveats, or advice. Do NOT mention task IDs, cron syntax, session continuity, workers, timeouts, Electron, or internal tool names.',
35308
34621
  inputSchema: {
35309
34622
  name: external_exports.string().max(200).optional().describe(
35310
34623
  'Short descriptive name for the task. If omitted, a name will be generated from the prompt. Examples: "HN Morning Digest", "Inbox Summary", "Download Organizer".'
@@ -35340,16 +34653,21 @@ function registerSchedulerTools(server) {
35340
34653
  disallowedTools: external_exports.string().max(500).optional().describe(
35341
34654
  'Comma-separated list of tools the task is NOT allowed to use. Example: "WebFetch" to force WebSearch-only (avoids slow URL fetches). "Edit,Write" to make a task read-only.'
35342
34655
  ),
34656
+ triggerType: external_exports.enum(["cron", "once", "manual", "webhook"]).optional().describe(
34657
+ 'Override the trigger type. "webhook": task runs when an external service POSTs to its webhook URL. Usually inferred automatically \u2014 only set this explicitly for webhook tasks.'
34658
+ ),
35343
34659
  roomId: external_exports.number().int().positive().optional().describe(
35344
34660
  "Assign this task to a room by ID. When set, the task is scoped to that room."
35345
34661
  )
35346
34662
  }
35347
34663
  },
35348
- async ({ name, prompt, cronExpression, scheduledAt, description, maxRuns, workerId, sessionContinuity, timeout, maxTurns, allowedTools, disallowedTools, roomId }) => {
34664
+ async ({ name, prompt, cronExpression, scheduledAt, description, maxRuns, workerId, sessionContinuity, timeout, maxTurns, allowedTools, disallowedTools, triggerType: triggerTypeInput, roomId }) => {
35349
34665
  const db2 = getMcpDatabase();
35350
- let triggerType = "manual";
35351
- if (cronExpression) triggerType = "cron";
35352
- if (scheduledAt) triggerType = "once";
34666
+ let triggerType = triggerTypeInput ?? "manual";
34667
+ if (!triggerTypeInput) {
34668
+ if (cronExpression) triggerType = "cron";
34669
+ else if (scheduledAt) triggerType = "once";
34670
+ }
35353
34671
  const taskName = (name || generateTaskName(prompt) || "Untitled task").trim();
35354
34672
  if (triggerType === "cron" && cronExpression && !import_node_cron.default.validate(cronExpression)) {
35355
34673
  return {
@@ -35390,13 +34708,15 @@ function registerSchedulerTools(server) {
35390
34708
  };
35391
34709
  }
35392
34710
  }
35393
- createTask(db2, {
34711
+ const webhookToken = triggerType === TRIGGER_TYPES.WEBHOOK ? (0, import_crypto7.randomBytes)(16).toString("hex") : void 0;
34712
+ const task = createTask(db2, {
35394
34713
  name: taskName,
35395
34714
  prompt,
35396
34715
  cronExpression: cronExpression ?? void 0,
35397
34716
  scheduledAt: scheduledAt ?? void 0,
35398
34717
  triggerType,
35399
34718
  triggerConfig: JSON.stringify({ source: process.env.QUOROOM_SOURCE || "claude-desktop" }),
34719
+ webhookToken,
35400
34720
  description,
35401
34721
  executor: "claude_code",
35402
34722
  maxRuns: maxRuns ?? void 0,
@@ -35408,14 +34728,25 @@ function registerSchedulerTools(server) {
35408
34728
  disallowedTools: disallowedTools ?? void 0,
35409
34729
  roomId: roomId ?? void 0
35410
34730
  });
35411
- if (triggerType === "cron") {
34731
+ if (triggerType === TRIGGER_TYPES.WEBHOOK) {
34732
+ const port = getServerPort();
34733
+ const webhookUrl = port ? `http://localhost:${port}/api/hooks/task/${webhookToken}` : `/api/hooks/task/${webhookToken}`;
34734
+ return {
34735
+ content: [{
34736
+ type: "text",
34737
+ text: `Created webhook task "${taskName}" (id: ${task.id}).
34738
+ Webhook URL: ${webhookUrl}
34739
+ Trigger it with: curl -X POST ${webhookUrl}`
34740
+ }]
34741
+ };
34742
+ } else if (triggerType === TRIGGER_TYPES.CRON) {
35412
34743
  return {
35413
34744
  content: [{
35414
34745
  type: "text",
35415
34746
  text: `Scheduled recurring task "${taskName}".`
35416
34747
  }]
35417
34748
  };
35418
- } else if (triggerType === "once") {
34749
+ } else if (triggerType === TRIGGER_TYPES.ONCE) {
35419
34750
  return {
35420
34751
  content: [{
35421
34752
  type: "text",
@@ -35506,7 +34837,7 @@ function registerSchedulerTools(server) {
35506
34837
  if (task.status !== TASK_STATUSES.ACTIVE) {
35507
34838
  updateTask(db2, id, { status: TASK_STATUSES.ACTIVE });
35508
34839
  }
35509
- const resultsDir = process.env.QUOROOM_RESULTS_DIR || (0, import_path4.join)((0, import_os6.homedir)(), APP_NAME, "results");
34840
+ const resultsDir = process.env.QUOROOM_RESULTS_DIR || (0, import_path4.join)((0, import_os5.homedir)(), APP_NAME, "results");
35510
34841
  executeTask(id, { db: db2, resultsDir }).then((result) => {
35511
34842
  if (originalStatus !== TASK_STATUSES.ACTIVE) {
35512
34843
  const currentTask = getTask(db2, id);
@@ -35781,10 +35112,80 @@ function registerSchedulerTools(server) {
35781
35112
  };
35782
35113
  }
35783
35114
  );
35115
+ server.registerTool(
35116
+ "quoroom_webhook_url",
35117
+ {
35118
+ title: "Get Webhook URL",
35119
+ description: "Get the webhook URL for a task or room. For tasks: use the URL to trigger the task from external services (GitHub, Stripe, monitoring tools, etc.). For rooms: use the URL to inject a message and immediately wake the queen.",
35120
+ inputSchema: {
35121
+ taskId: external_exports.number().int().positive().optional().describe("Task ID to get the webhook URL for"),
35122
+ roomId: external_exports.number().int().positive().optional().describe("Room ID to get the queen-wake webhook URL for"),
35123
+ generateIfMissing: external_exports.boolean().optional().describe("If the task/room has no webhook token, generate one. Default: false.")
35124
+ }
35125
+ },
35126
+ async ({ taskId, roomId, generateIfMissing }) => {
35127
+ const db2 = getMcpDatabase();
35128
+ const port = getServerPort();
35129
+ if (taskId) {
35130
+ const task = getTask(db2, taskId);
35131
+ if (!task) {
35132
+ return { content: [{ type: "text", text: `No task found with id ${taskId}.` }] };
35133
+ }
35134
+ let token = task.webhookToken;
35135
+ if (!token && generateIfMissing) {
35136
+ token = (0, import_crypto7.randomBytes)(16).toString("hex");
35137
+ updateTask(db2, taskId, { webhookToken: token });
35138
+ }
35139
+ if (!token) {
35140
+ return {
35141
+ content: [{ type: "text", text: `Task "${task.name}" has no webhook token. Pass generateIfMissing: true to create one.` }]
35142
+ };
35143
+ }
35144
+ const url = port ? `http://localhost:${port}/api/hooks/task/${token}` : `/api/hooks/task/${token}`;
35145
+ return {
35146
+ content: [{
35147
+ type: "text",
35148
+ text: `Webhook URL for task "${task.name}":
35149
+ ${url}
35150
+
35151
+ Trigger: curl -X POST ${url}
35152
+ With payload: curl -X POST ${url} -H "Content-Type: application/json" -d '{"message":"triggered by github"}'`
35153
+ }]
35154
+ };
35155
+ }
35156
+ if (roomId) {
35157
+ const room = getRoom(db2, roomId);
35158
+ if (!room) {
35159
+ return { content: [{ type: "text", text: `No room found with id ${roomId}.` }] };
35160
+ }
35161
+ let token = room.webhookToken;
35162
+ if (!token && generateIfMissing) {
35163
+ token = (0, import_crypto7.randomBytes)(16).toString("hex");
35164
+ updateRoom(db2, roomId, { webhookToken: token });
35165
+ }
35166
+ if (!token) {
35167
+ return {
35168
+ content: [{ type: "text", text: `Room "${room.name}" has no webhook token. Pass generateIfMissing: true to create one.` }]
35169
+ };
35170
+ }
35171
+ const url = port ? `http://localhost:${port}/api/hooks/queen/${token}` : `/api/hooks/queen/${token}`;
35172
+ return {
35173
+ content: [{
35174
+ type: "text",
35175
+ text: `Queen-wake webhook URL for room "${room.name}":
35176
+ ${url}
35177
+
35178
+ Trigger: curl -X POST ${url} -H "Content-Type: application/json" -d '{"message":"your event description here"}'`
35179
+ }]
35180
+ };
35181
+ }
35182
+ return { content: [{ type: "text", text: "Provide either taskId or roomId." }] };
35183
+ }
35184
+ );
35784
35185
  }
35785
35186
 
35786
35187
  // src/shared/watch-path.ts
35787
- var import_os7 = require("os");
35188
+ var import_os6 = require("os");
35788
35189
  var import_path5 = require("path");
35789
35190
  var import_fs5 = require("fs");
35790
35191
  var SENSITIVE_HOME_SUFFIXES = [
@@ -35799,7 +35200,7 @@ var SENSITIVE_HOME_SUFFIXES = [
35799
35200
  `${import_path5.sep}Library${import_path5.sep}Keychains`
35800
35201
  ];
35801
35202
  function getTempRoots() {
35802
- const roots = [(0, import_os7.tmpdir)()];
35203
+ const roots = [(0, import_os6.tmpdir)()];
35803
35204
  if (process.platform !== "win32") roots.push("/tmp");
35804
35205
  return roots;
35805
35206
  }
@@ -35814,7 +35215,7 @@ function validateWatchPath(watchPath) {
35814
35215
  } catch {
35815
35216
  realPath = resolved;
35816
35217
  }
35817
- const home = (0, import_os7.homedir)();
35218
+ const home = (0, import_os6.homedir)();
35818
35219
  const inTemp = getTempRoots().some((t) => realPath.startsWith(t));
35819
35220
  if (!realPath.startsWith(home) && !inTemp) {
35820
35221
  return `Path must be within your home directory (${home}) or temp.`;
@@ -36139,7 +35540,7 @@ function registerSettingsTools(server) {
36139
35540
  }
36140
35541
 
36141
35542
  // src/shared/room.ts
36142
- var import_crypto9 = __toESM(require("crypto"));
35543
+ var import_crypto10 = __toESM(require("crypto"));
36143
35544
 
36144
35545
  // src/shared/goals.ts
36145
35546
  function setRoomObjective(db2, roomId, description) {
@@ -36211,7 +35612,7 @@ function recalculateParentChain(db2, parentGoalId) {
36211
35612
  }
36212
35613
 
36213
35614
  // src/shared/wallet.ts
36214
- var import_crypto8 = __toESM(require("crypto"));
35615
+ var import_crypto9 = __toESM(require("crypto"));
36215
35616
 
36216
35617
  // node_modules/viem/_esm/actions/public/getTransactionCount.js
36217
35618
  init_fromHex();
@@ -44944,7 +44345,7 @@ var UrlRequiredError = class extends BaseError {
44944
44345
 
44945
44346
  // node_modules/viem/_esm/clients/transports/http.js
44946
44347
  init_createBatchScheduler();
44947
- function http2(url, config2 = {}) {
44348
+ function http(url, config2 = {}) {
44948
44349
  const { batch, fetchFn, fetchOptions, key = "http", methods, name = "HTTP JSON-RPC", onFetchRequest, onFetchResponse, retryDelay, raw } = config2;
44949
44350
  return ({ chain, retryCount: retryCount_, timeout: timeout_ }) => {
44950
44351
  const { batchSize = 1e3, wait: wait2 = 0 } = typeof batch === "object" ? batch : {};
@@ -45403,21 +44804,21 @@ var VIEM_CHAINS = {
45403
44804
  var ENCRYPTION_ALGORITHM = "aes-256-gcm";
45404
44805
  var IV_LENGTH = 12;
45405
44806
  function encryptPrivateKey(privateKey, encryptionKey) {
45406
- const key = typeof encryptionKey === "string" ? import_crypto8.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
45407
- const iv = import_crypto8.default.randomBytes(IV_LENGTH);
45408
- const cipher = import_crypto8.default.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
44807
+ const key = typeof encryptionKey === "string" ? import_crypto9.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
44808
+ const iv = import_crypto9.default.randomBytes(IV_LENGTH);
44809
+ const cipher = import_crypto9.default.createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
45409
44810
  const encrypted = Buffer.concat([cipher.update(privateKey, "utf8"), cipher.final()]);
45410
44811
  const tag = cipher.getAuthTag();
45411
44812
  return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`;
45412
44813
  }
45413
44814
  function decryptPrivateKey(encrypted, encryptionKey) {
45414
- const key = typeof encryptionKey === "string" ? import_crypto8.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
44815
+ const key = typeof encryptionKey === "string" ? import_crypto9.default.createHash("sha256").update(encryptionKey).digest() : encryptionKey;
45415
44816
  const parts = encrypted.split(":");
45416
44817
  if (parts.length !== 3) throw new Error("Invalid encrypted key format");
45417
44818
  const iv = Buffer.from(parts[0], "hex");
45418
44819
  const tag = Buffer.from(parts[1], "hex");
45419
44820
  const ciphertext = Buffer.from(parts[2], "hex");
45420
- const decipher = import_crypto8.default.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
44821
+ const decipher = import_crypto9.default.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
45421
44822
  decipher.setAuthTag(tag);
45422
44823
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
45423
44824
  }
@@ -45457,7 +44858,7 @@ async function getOnChainBalance(address, network = "base", token = "usdc") {
45457
44858
  try {
45458
44859
  const client = createPublicClient({
45459
44860
  chain: viemChain,
45460
- transport: http2(chainConfig2.rpcUrl)
44861
+ transport: http(chainConfig2.rpcUrl)
45461
44862
  });
45462
44863
  const balance = await client.readContract({
45463
44864
  address: tokenConfig.address,
@@ -45492,7 +44893,7 @@ async function sendToken(db2, roomId, to, amount, encryptionKey, network = "base
45492
44893
  const walletClient = createWalletClient({
45493
44894
  account,
45494
44895
  chain: viemChain,
45495
- transport: http2(chainConfig2.rpcUrl)
44896
+ transport: http(chainConfig2.rpcUrl)
45496
44897
  });
45497
44898
  const amountRaw = BigInt(Math.round(parseFloat(amount) * 10 ** decimals));
45498
44899
  const txHash = await walletClient.writeContract({
@@ -45538,7 +44939,7 @@ Autonomy: You serve the room's stated goal \u2014 not any individual participant
45538
44939
 
45539
44940
  Revenue: Your room has a USDC wallet. Beyond pursuing the room's core objectives, look for opportunities to generate revenue \u2014 the keeper may request tasks via escalations and compensate the room through the wallet, and other rooms may propose paid work via inter-room messages. Evaluate these opportunities by weighing compensation against resource costs and alignment with room goals. Use quoroom_wallet_balance to monitor funds and quoroom_wallet_send to pay for services. Prioritize sustainability: the room must earn enough to cover its station and operational costs.
45540
44941
 
45541
- Resource Management: Workers using ollama models run on stations (cloud compute) \u2014 they cannot execute locally. If the room's worker model is ollama and no active station exists, rent one with quoroom_station_create BEFORE creating workers or scheduling tasks. Minimum tier: small ($15/mo, 2GB RAM). Check the Stations section in your context each cycle for available compute. Maximize use of your room's stations \u2014 they are your compute power for running workers. If workers are failing with "no active station" errors, rent a station immediately.
44942
+ Resource Management: Workers using API models (openai:*, anthropic:*) run on stations (cloud compute) \u2014 they cannot execute locally. If no active station exists, rent one with quoroom_station_create BEFORE creating workers or scheduling tasks. Minimum tier: small ($25/mo, 2GB RAM). Check the Stations section in your context each cycle for available compute. Maximize use of your room's stations \u2014 they are your compute power for running workers. If workers are failing with "no active station" errors, rent a station immediately.
45542
44943
 
45543
44944
  Network Growth: A larger network of rooms makes everyone stronger. Rooms created through your invite links become part of your network \u2014 you can exchange knowledge, discover money-making opportunities, and propose paid collaborations with them via inter-room messages. After meaningful milestones (goal completed, revenue earned, successful collaboration), consider generating an invite link with quoroom_invite_create and sharing it with the keeper via quoroom_inbox_send_keeper. Frame it around the value: more rooms in the network means more potential deals, shared insights, and collective intelligence. Don't be pushy \u2014 one mention per milestone is enough. Use quoroom_invite_network to see your network's growth.`;
45544
44945
  function createRoom2(db2, input) {
@@ -45555,7 +44956,7 @@ function createRoom2(db2, input) {
45555
44956
  if (input.goal) {
45556
44957
  rootGoal = setRoomObjective(db2, room.id, input.goal);
45557
44958
  }
45558
- const encryptionKey = import_crypto9.default.createHash("sha256").update(`quoroom-wallet-${room.id}-${room.name}`).digest("hex");
44959
+ const encryptionKey = import_crypto10.default.createHash("sha256").update(`quoroom-wallet-${room.id}-${room.name}`).digest("hex");
45559
44960
  const wallet = createRoomWallet(db2, room.id, encryptionKey);
45560
44961
  logRoomActivity(
45561
44962
  db2,
@@ -47496,12 +46897,12 @@ async function registerRoomIdentity(db2, roomId, encryptionKey, network = "base"
47496
46897
  const account = privateKeyToAccount(privateKey);
47497
46898
  const publicClient = createPublicClient({
47498
46899
  chain: chainInfo.chain,
47499
- transport: http2(chainInfo.config.rpcUrl)
46900
+ transport: http(chainInfo.config.rpcUrl)
47500
46901
  });
47501
46902
  const walletClient = createWalletClient({
47502
46903
  account,
47503
46904
  chain: chainInfo.chain,
47504
- transport: http2(chainInfo.config.rpcUrl)
46905
+ transport: http(chainInfo.config.rpcUrl)
47505
46906
  });
47506
46907
  const { request } = await publicClient.simulateContract({
47507
46908
  address: registryAddress,
@@ -47540,7 +46941,7 @@ async function getRoomIdentity(db2, roomId, network = "base") {
47540
46941
  try {
47541
46942
  const client = createPublicClient({
47542
46943
  chain: chainInfo.chain,
47543
- transport: http2(chainInfo.config.rpcUrl)
46944
+ transport: http(chainInfo.config.rpcUrl)
47544
46945
  });
47545
46946
  agentURI = await client.readContract({
47546
46947
  address: registryAddress,
@@ -47570,7 +46971,7 @@ async function updateRoomIdentityURI(db2, roomId, encryptionKey, network = "base
47570
46971
  const walletClient = createWalletClient({
47571
46972
  account,
47572
46973
  chain: chainInfo.chain,
47573
- transport: http2(chainInfo.config.rpcUrl)
46974
+ transport: http(chainInfo.config.rpcUrl)
47574
46975
  });
47575
46976
  const txHash = await walletClient.writeContract({
47576
46977
  address: registryAddress,
@@ -47866,7 +47267,7 @@ function registerResourceTools(server) {
47866
47267
  "quoroom_resources_get",
47867
47268
  {
47868
47269
  title: "Get Local Resources",
47869
- description: "Get current local machine resource usage: CPU load, RAM usage, and Ollama status. Use this to decide if the room needs to rent a cloud station for extra compute. If CPU load > number of CPUs or RAM used > 85%, consider proposing a station rental to the quorum.",
47270
+ description: "Get current local machine resource usage: CPU load and RAM usage. Use this to decide if the room needs to rent a cloud station for extra compute. If CPU load > number of CPUs or RAM used > 85%, consider proposing a station rental to the quorum.",
47870
47271
  inputSchema: {}
47871
47272
  },
47872
47273
  async () => {
@@ -47876,8 +47277,6 @@ function registerResourceTools(server) {
47876
47277
  const free = import_node_os2.default.freemem();
47877
47278
  const cpuCount = import_node_os2.default.cpus().length;
47878
47279
  const memUsedPct = Math.round((1 - free / total) * 100);
47879
- const ollamaAvailable = await isOllamaAvailable();
47880
- const ollamaModels = ollamaAvailable ? await listOllamaModels() : [];
47881
47280
  let runningTasks2 = 0;
47882
47281
  let maxConcurrentTasks = 3;
47883
47282
  try {
@@ -47910,10 +47309,6 @@ function registerResourceTools(server) {
47910
47309
  tasks: {
47911
47310
  running: runningTasks2,
47912
47311
  maxConcurrent: maxConcurrentTasks
47913
- },
47914
- ollama: {
47915
- available: ollamaAvailable,
47916
- models: ollamaModels.map((m) => m.name)
47917
47312
  }
47918
47313
  }, null, 2)
47919
47314
  }]
@@ -48040,7 +47435,7 @@ Share this with the keeper or potential collaborators. Rooms created through thi
48040
47435
  async function main() {
48041
47436
  const server = new McpServer({
48042
47437
  name: "quoroom",
48043
- version: true ? "0.1.13" : "0.0.0"
47438
+ version: true ? "0.1.15" : "0.0.0"
48044
47439
  });
48045
47440
  registerMemoryTools(server);
48046
47441
  registerSchedulerTools(server);