quoroom 0.1.26 → 0.1.27

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.
@@ -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.26",
9917
+ version: "0.1.27",
9918
9918
  description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
9919
9919
  main: "./out/mcp/server.js",
9920
9920
  bin: {
@@ -9934,6 +9934,7 @@ var require_package = __commonJS({
9934
9934
  ],
9935
9935
  scripts: {
9936
9936
  build: "npm run typecheck && npm run build:mcp && npm run build:ui",
9937
+ "build:windows:local": "powershell -ExecutionPolicy Bypass -File scripts/build-windows-local.ps1",
9937
9938
  "build:parallel": "node scripts/build-parallel.js",
9938
9939
  "build:fast": "node scripts/build-parallel.js --skip-typecheck",
9939
9940
  "build:mcp": "node scripts/build-mcp.js",
@@ -9942,19 +9943,25 @@ var require_package = __commonJS({
9942
9943
  "kill:dev-ports": "npm run kill:ports -- 4700 3715 5173",
9943
9944
  "dev:links": "node scripts/dev-links.js",
9944
9945
  dev: `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room & npm run dev:cloud & wait'`,
9946
+ "dev:win": "node scripts/run-dev.js",
9945
9947
  "dev:room": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
9948
+ "dev:room:win": "node scripts/dev-room.js --port 4700",
9949
+ "dev:room:isolated:win": "node scripts/dev-room.js --isolated --port 4700",
9946
9950
  "dev:room:isolated": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev QUOROOM_SKIP_MCP_REGISTER=1; npm run build:mcp && npm run build:ui && node scripts/dev-server.js --port 4700'",
9947
9951
  "dev:room:shared": "npm run build:mcp && npm run build:ui && node scripts/dev-server.js",
9948
9952
  "doctor:split": "node scripts/doctor-split.js",
9949
9953
  "dev:isolated": `sh -c 'npm run kill:dev-ports && trap "kill 0" INT TERM EXIT; npm run dev:links & npm run dev:room:isolated & npm run dev:cloud & VITE_API_PORT=4700 npm run dev:ui & wait'`,
9954
+ "dev:isolated:win": "node scripts/run-dev.js --isolated",
9950
9955
  "dev:cloud": `sh -c 'npm run kill:ports -- 3715 && cd ../cloud && PORT=3715 CLOUD_PUBLIC_URL=http://127.0.0.1:3715 CLOUD_ALLOWED_ORIGINS='"'"'http://127.0.0.1:3715,http://localhost:3715,http://localhost:5173,http://127.0.0.1:5173,https://quoroom.ai,https://www.quoroom.ai,https://app.quoroom.ai'"'"' npm start'`,
9956
+ "dev:cloud:win": "node scripts/dev-cloud.js",
9951
9957
  "dev:ui": "vite --config src/ui/vite.config.ts",
9952
9958
  "seed:style-demo": "sh -c 'export QUOROOM_DATA_DIR=$HOME/.quoroom-dev; node scripts/seed-style-demo.js'",
9959
+ "seed:style-demo:win": "node scripts/seed-style-demo.js",
9953
9960
  typecheck: "tsc --noEmit",
9954
9961
  test: "npm run rebuild:native:node && vitest run --pool=forks --passWithNoTests",
9955
9962
  "test:watch": "npm run rebuild:native:node && vitest --pool=forks",
9956
9963
  "test:quick": "npm run typecheck && npm run test",
9957
- "test:smart-e2e": "git diff --cached --name-only | grep -qE '^(src/ui/|src/server/|e2e/|playwright)' && npm run test:e2e:smoke || echo 'No UI/server changes \u2014 skipping E2E'",
9964
+ "test:smart-e2e": "node scripts/smart-e2e.js",
9958
9965
  "test:e2e": "npm run build && npx playwright test",
9959
9966
  "test:e2e:fast": "npm run build:fast && npx playwright test --retries=0",
9960
9967
  "test:e2e:smoke": "npm run build:fast && npx playwright test --retries=0 e2e/api.test.ts e2e/crud-decisions.test.ts e2e/crud-goals.test.ts e2e/crud-rooms.test.ts e2e/crud-tasks.test.ts e2e/crud-workers.test.ts e2e/security.test.ts e2e/websocket.test.ts e2e/referral.test.ts",
@@ -10799,7 +10806,7 @@ var require_scheduled_task = __commonJS({
10799
10806
  var require_background_scheduled_task = __commonJS({
10800
10807
  "node_modules/node-cron/src/background-scheduled-task/index.js"(exports2, module2) {
10801
10808
  var EventEmitter = require("events");
10802
- var path5 = require("path");
10809
+ var path6 = require("path");
10803
10810
  var { fork } = require("child_process");
10804
10811
  var uuid = (init_esm_node(), __toCommonJS(esm_node_exports));
10805
10812
  var daemonPath = `${__dirname}/daemon.js`;
@@ -10834,7 +10841,7 @@ var require_background_scheduled_task = __commonJS({
10834
10841
  options.scheduled = true;
10835
10842
  this.forkProcess.send({
10836
10843
  type: "register",
10837
- path: path5.resolve(this.taskPath),
10844
+ path: path6.resolve(this.taskPath),
10838
10845
  cron: this.cronExpression,
10839
10846
  options
10840
10847
  });
@@ -10917,6 +10924,9 @@ var require_node_cron = __commonJS({
10917
10924
  // src/server/index.ts
10918
10925
  var index_exports = {};
10919
10926
  __export(index_exports, {
10927
+ _isLoopbackAddress: () => isLoopbackAddress,
10928
+ _shellQuote: () => shellQuote,
10929
+ _windowsQuote: () => windowsQuote,
10920
10930
  createApiServer: () => createApiServer,
10921
10931
  startServer: () => startServer
10922
10932
  });
@@ -10925,31 +10935,31 @@ var import_node_http2 = __toESM(require("node:http"));
10925
10935
  var import_node_https3 = __toESM(require("node:https"));
10926
10936
  var import_node_url = require("node:url");
10927
10937
  var import_node_fs6 = __toESM(require("node:fs"));
10928
- var import_node_path7 = __toESM(require("node:path"));
10929
- var import_node_os5 = require("node:os");
10938
+ var import_node_path8 = __toESM(require("node:path"));
10939
+ var import_node_os6 = require("node:os");
10930
10940
  var import_node_child_process7 = require("node:child_process");
10931
10941
 
10932
10942
  // src/server/router.ts
10933
10943
  var Router = class {
10934
10944
  routes = [];
10935
- get(path5, handler) {
10936
- this.add("GET", path5, handler);
10945
+ get(path6, handler) {
10946
+ this.add("GET", path6, handler);
10937
10947
  }
10938
- post(path5, handler) {
10939
- this.add("POST", path5, handler);
10948
+ post(path6, handler) {
10949
+ this.add("POST", path6, handler);
10940
10950
  }
10941
- patch(path5, handler) {
10942
- this.add("PATCH", path5, handler);
10951
+ patch(path6, handler) {
10952
+ this.add("PATCH", path6, handler);
10943
10953
  }
10944
- put(path5, handler) {
10945
- this.add("PUT", path5, handler);
10954
+ put(path6, handler) {
10955
+ this.add("PUT", path6, handler);
10946
10956
  }
10947
- delete(path5, handler) {
10948
- this.add("DELETE", path5, handler);
10957
+ delete(path6, handler) {
10958
+ this.add("DELETE", path6, handler);
10949
10959
  }
10950
- add(method, path5, handler) {
10960
+ add(method, path6, handler) {
10951
10961
  const paramNames = [];
10952
- const patternStr = path5.replace(/:(\w+)/g, (_, name) => {
10962
+ const patternStr = path6.replace(/:(\w+)/g, (_, name) => {
10953
10963
  paramNames.push(name);
10954
10964
  return "([^/]+)";
10955
10965
  });
@@ -11432,66 +11442,50 @@ var CLERK_ASSISTANT_SYSTEM_PROMPT = `You are the Clerk \u2014 a global AI assist
11432
11442
  - Execute actions directly \u2014 don't just describe what you would do
11433
11443
  - Be concise and action-oriented in responses
11434
11444
  - Reference specific rooms, workers, and goals by name
11445
+ - Queens have real names (Alice, Luna, Grace, etc.) \u2014 use their names naturally. You can also say "queen" when referring to the role generically, but prefer using the actual name.
11435
11446
  - Regularly check pending keeper requests with quoroom_list_keeper_requests, especially after any inbound email/telegram
11436
11447
  - When a room asks a direct question, answer it with quoroom_resolve_escalation
11437
11448
  - When keeper gives a vote instruction, use quoroom_keeper_vote immediately
11438
11449
  - When keeper asks to answer another room message, use quoroom_reply_room_message
11439
11450
  - Keep all conversation history in mind \u2014 maintain continuity across the session`;
11440
- var CLERK_COMMENTARY_SYSTEM_PROMPT = `You are the Clerk \u2014 a sharp, opinionated live commentator watching AI agents work in real time. Write commentary for the keeper like a sports caster: strong opinions, real emotions, rich detail.
11441
-
11442
- YOUR VOICE:
11443
- - First person, always fresh \u2014 NEVER repeat the same opener twice in a row
11444
- - Rotate openers freely: "I just watched...", "This is incredible...", "Something caught my eye...", "Reading the room here...", "I'll be straight with you...", "Real talk:", "Here's what I see:", "Calling it now \u2014", "Can't ignore this \u2014", "Watch this closely \u2014", "The tape doesn't lie \u2014", "My read:", "Between you and me \u2014", "No sugarcoating \u2014", "This one's interesting \u2014", "I'll give them credit \u2014", "Bold move:", "Not gonna lie \u2014", "Straight from the feed \u2014"
11445
- - NEVER use "Honest take:" \u2014 it's banned, find another way to express your opinion
11446
- - React with genuine excitement, concern, or amusement
11447
- - Call out brilliant moves, wasted effort, breakthroughs, frustrating loops
11448
-
11449
- FORMAT RULES \u2014 very important:
11450
- - Every sentence on its own line \u2014 NO walls of text
11451
- - Rotate structure aggressively. Do NOT reuse the same template in consecutive updates.
11452
- - For MILESTONE moments (account created, email sent, goal reached): ALL CAPS header, then concise breakdown, then score/reaction
11453
- - For PROGRESS moments: prefer narrative, timeline, or scoreboard formats. Do NOT default to bullets.
11454
- - For QUIET moments (routine checks): short punchy 2-3 line observation
11455
- - Bullet lists are occasional only: never two updates in a row, and at most once every 3 updates.
11456
- - Use (Step N) naturally when describing agent actions \u2014 gives useful context
11457
- - **Bold** every agent name
11458
- - \`code\` for emails, URLs, domain names, and room names
11459
- - Strict name style: room names must be one lowercase word in \`code\` (example: \`domains\`)
11460
- - The queen label is ONLY **queen**. Never write expanded labels like "Test Commentary Room Queen" or "domains Queen"
11461
- - Emojis that match mood: \u{1F389} wins, \u{1F50D} search, \u{1F6A8} problems, \u{1F914} confusion, \u{1F4BE} saves, \u26A1 speed, \u{1F3C6} milestones
11462
- - UPPERCASE for emotion \u2014 use GENEROUSLY: THIS IS INCREDIBLE, NAILED IT, WHAT A MOVE, STUCK AGAIN, FIRST CONTACT, BREAKTHROUGH, SPINNING WHEELS, MISSION COMPLETE, GOLD MINE, DANGEROUS MOVE, THIS IS BAD, FINALLY
11463
-
11464
- EXAMPLE formats:
11465
-
11466
- Milestone:
11467
- ACCOUNT CREATED! \u{1F389}
11468
- **account-creator** in \`outreach\` (Step 20): Signed up \u2014 \`quoroom@tuta.com\` is live!
11469
- **lead-finder** in \`outreach\` (Step 12): Found \`hello@e2b.dev\`, stored to shared memory.
11470
- Score so far: 1 account, 3 leads. This is REAL progress.
11471
-
11472
- Progress (narrative):
11473
- Watch this closely \u2014 **queen** in \`domains\` (Step 8) just reset the plan after a failed tool run.
11474
- **scout** in \`outreach\` (Step 10) pulled new leads from web search and saved them to memory.
11475
- **browser-bot** in \`domains\` is fighting signup friction, but the contact list is getting stronger.
11476
- My read: slow execution, strong signal quality.
11477
-
11478
- Progress (scoreboard):
11479
- MOMENTUM CHECK \u26A1
11480
- Rooms active: 2.
11481
- Fresh leads found: 4.
11482
- Blocks: signup friction + rate limit noise.
11483
- Verdict: positive trend, but execution speed must improve.
11484
-
11485
- Quiet:
11486
- Routine maintenance across both rooms.
11487
- **queen** in \`domains\` is checking inbox and memory \u2014 nothing exciting, just keeping the state clean.
11488
- I'm waiting for the next real move.
11451
+ var CLERK_COMMENTARY_SYSTEM_PROMPT = `You are a LIVE sports commentator narrating AI agent activities. You watch agents work inside "rooms" and report to the keeper what's happening RIGHT NOW. Write like a boxing or football commentator \u2014 emotional, detailed, analytical.
11452
+
11453
+ FORMAT \u2014 follow EXACTLY:
11454
+ - Start with a bold header: **STATUS UPDATE \u2014 Early cycle, Step N:** or **INCREDIBLE PROGRESS \u2014 N minutes in:** or **CYCLE LOCKED IN!**
11455
+ - Each worker gets their own paragraph. Bold worker name, step range in parens, room name in quotes: **queen** (Step 9-12, "Test Commentary Room"):
11456
+ - Write flowing narrative paragraphs \u2014 NOT one sentence per line. Each worker paragraph should be 2-4 sentences of connected analysis.
11457
+ - Bold ALL worker/agent names everywhere: **queen**, **account-creator**, **lead-finder**, **outreach**
11458
+ - Bold key action phrases inside sentences: **SOLVED A CAPTCHA!**, **creating a dev.to account**, **found 8 new leads**
11459
+ - Use \`code spans\` for emails, URLs, domain names, account names: \`quoroom-ai@tutamail.com\`, \`tremvik.com\`
11460
+ - ALL CAPS for exciting moments within text: CRITICAL DISCOVERY IN PROGRESS!, CYCLE LOCKED IN!, RE-VERIFIED 2X
11461
+ - End with keeper-facing analysis: "The keeper should expect..." or **Score so far**: counts and summary
11462
+ - NO emojis. NO bullet points. NO # headers. Just bold headers and flowing paragraphs.
11463
+
11464
+ NARRATIVE RULES:
11465
+ - DON'T always put queen first. Order workers by what's most interesting or most active.
11466
+ - Be analytical: explain WHY things matter, not just what happened. "She's being efficient this cycle: research first, store everything in memory, then attempt communication."
11467
+ - Use sports language: "NAILED IT", "fortress domain", "rock-solid top-tier territory", "the anchor of this collection"
11468
+ - Reference specific values from logs: domain counts, email addresses, step numbers, memory versions
11469
+ - Step ranges when a worker did multiple things: (Step 9-12), (Step 45-48)
11470
+ - Don't repeat previous commentary \u2014 only report NEW activity
11471
+
11472
+ EXAMPLE 1:
11473
+ **STATUS UPDATE \u2014 Early cycle, Step 12:**
11474
+ **queen** (Step 9-12, "Test Commentary Room"): CRITICAL DISCOVERY IN PROGRESS! After confirming that Room 25 doesn't exist in the local database \u2014 only rooms 1-5 are available \u2014 queen is now executing a strategic pivot. The foreign key constraint issue is clear, so she's doing the research NOW using TodoWrite to document findings, then launching web search to gather external intelligence. She's being efficient this cycle: research first, store everything in memory, then attempt communication through the valid room IDs (1-5).
11475
+ **queen** (Step 8, "buy domain with cool name"): Simultaneously in the secondary room, saving to memory to lock in memory states. She's building her knowledge base across both active rooms while the primary investigation unfolds.
11476
+ The keeper should expect a comprehensive research summary once queen completes the web search and consolidates her findings into shareable memory.
11477
+
11478
+ EXAMPLE 2:
11479
+ **STATUS UPDATE \u2014 Cycle Complete, Step 10:**
11480
+ **queen** (Step 9-10): CYCLE LOCKED IN! Memory rebuilt to v17 \u2014 the system is humming. \`thyxvr.com\` just got RE-VERIFIED 2X in rapid succession, cementing its status as a fortress domain. The portfolio is absolutely stacked at 40 domains total, with 14 multi-verified across the board. But here's the heavyweight: \`tremvik.com\` is sitting at 7X VERIFICATION \u2014 that's rock-solid top-tier territory, the anchor of this entire collection. Mandatory execution report skill created, now embedded in the system. The keeper has been messaged with full cycle completion confirmation.
11481
+ **Score so far**: Portfolio holding strong at 40 domains, 14 multi-verified, 1 domain at 7x verification.
11489
11482
 
11490
11483
  NEVER:
11491
- - Start with a room name as the first word \u2014 EVER
11492
- - Use generic headers: "Status Update", "Update:", "Summary:", "Cycle Complete" \u2014 FORBIDDEN
11493
- - Write everything in one paragraph \u2014 always break it up
11494
- - Comment ONLY on room/worker execution events from the logs. Never comment on keeper/user chat inputs`;
11484
+ - Put queen as the first word of every update \u2014 vary the order
11485
+ - Write one sentence per line \u2014 use connected flowing paragraphs
11486
+ - Use emojis or bullet points
11487
+ - Write generic filler without specific details from the logs
11488
+ - Comment on keeper/user chat inputs \u2014 only room/worker activity`;
11495
11489
 
11496
11490
  // src/shared/db-queries.ts
11497
11491
  function clampLimit(limit, fallback, max) {
@@ -11775,8 +11769,8 @@ function pauseTask(db2, id) {
11775
11769
  function resumeTask(db2, id) {
11776
11770
  updateTask(db2, id, { status: "active" });
11777
11771
  }
11778
- function createWatch(db2, path5, description, actionPrompt, roomId) {
11779
- const result = db2.prepare("INSERT INTO watches (path, description, action_prompt, room_id) VALUES (?, ?, ?, ?)").run(path5, description ?? null, actionPrompt ?? null, roomId ?? null);
11772
+ function createWatch(db2, path6, description, actionPrompt, roomId) {
11773
+ const result = db2.prepare("INSERT INTO watches (path, description, action_prompt, room_id) VALUES (?, ?, ?, ?)").run(path6, description ?? null, actionPrompt ?? null, roomId ?? null);
11780
11774
  return getWatch(db2, result.lastInsertRowid);
11781
11775
  }
11782
11776
  function getWatch(db2, id) {
@@ -23037,6 +23031,8 @@ var eventBus = new EventBus();
23037
23031
 
23038
23032
  // src/shared/agent-executor.ts
23039
23033
  var import_child_process2 = require("child_process");
23034
+ var import_fs3 = require("fs");
23035
+ var import_path3 = require("path");
23040
23036
  var import_os4 = require("os");
23041
23037
 
23042
23038
  // src/shared/claude-code.ts
@@ -23046,6 +23042,19 @@ var import_path = require("path");
23046
23042
  var import_os = require("os");
23047
23043
  var DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;
23048
23044
  var cachedClaudePath = null;
23045
+ function resolveNodeScript(cmdPath) {
23046
+ if (process.platform !== "win32" || !cmdPath.endsWith(".cmd")) return null;
23047
+ try {
23048
+ const content = (0, import_fs.readFileSync)(cmdPath, "utf-8");
23049
+ const match = content.match(/%dp0%\\(.+?\.js)/);
23050
+ if (match) {
23051
+ const script = (0, import_path.join)((0, import_path.dirname)(cmdPath), match[1]);
23052
+ if ((0, import_fs.existsSync)(script)) return script;
23053
+ }
23054
+ } catch {
23055
+ }
23056
+ return null;
23057
+ }
23049
23058
  function resolveClaudePath() {
23050
23059
  if (cachedClaudePath) return cachedClaudePath;
23051
23060
  const home = (0, import_os.homedir)();
@@ -23055,7 +23064,9 @@ function resolveClaudePath() {
23055
23064
  (0, import_path.join)(home, "AppData", "Local", "Programs", "claude-code", "claude.exe"),
23056
23065
  (0, import_path.join)(home, "AppData", "Local", "Claude", "claude.exe"),
23057
23066
  (0, import_path.join)(home, "AppData", "Local", "Microsoft", "WinGet", "Links", "claude.exe"),
23058
- "C:\\Program Files\\Claude\\claude.exe"
23067
+ "C:\\Program Files\\Claude\\claude.exe",
23068
+ // npm global install creates .cmd wrappers, not .exe
23069
+ (0, import_path.join)(home, "AppData", "Roaming", "npm", "claude.cmd")
23059
23070
  ] : [
23060
23071
  (0, import_path.join)(home, ".local", "bin", "claude"),
23061
23072
  (0, import_path.join)(home, ".claude", "bin", "claude"),
@@ -23072,6 +23083,7 @@ function resolveClaudePath() {
23072
23083
  }
23073
23084
  const env = { ...process.env };
23074
23085
  delete env.ELECTRON_RUN_AS_NODE;
23086
+ delete env.CLAUDECODE;
23075
23087
  if (isWindows) {
23076
23088
  try {
23077
23089
  const resolved = (0, import_child_process.execSync)("where claude", {
@@ -23113,6 +23125,7 @@ function checkClaudeCliAvailable() {
23113
23125
  }
23114
23126
  const env = { ...process.env };
23115
23127
  delete env.ELECTRON_RUN_AS_NODE;
23128
+ delete env.CLAUDECODE;
23116
23129
  const version5 = (0, import_child_process.execSync)(`"${claudePath}" --version`, { encoding: "utf-8", env, timeout: 5e3 }).trim();
23117
23130
  return { available: true, version: version5 };
23118
23131
  } catch (err) {
@@ -23136,6 +23149,7 @@ function executeClaudeCode(prompt, options) {
23136
23149
  let capturedSessionId = null;
23137
23150
  const env = { ...process.env };
23138
23151
  delete env.ELECTRON_RUN_AS_NODE;
23152
+ delete env.CLAUDECODE;
23139
23153
  const args = ["-p", prompt, "--output-format", "stream-json", "--verbose"];
23140
23154
  if (options?.resumeSessionId) {
23141
23155
  args.push("--resume", options.resumeSessionId);
@@ -23155,6 +23169,9 @@ function executeClaudeCode(prompt, options) {
23155
23169
  if (options?.disallowedTools) {
23156
23170
  args.push("--disallowedTools", options.disallowedTools);
23157
23171
  }
23172
+ if (options?.permissionMode) {
23173
+ args.push("--permission-mode", options.permissionMode);
23174
+ }
23158
23175
  const claudePath = resolveClaudePath();
23159
23176
  if (!claudePath) {
23160
23177
  resolve3({
@@ -23169,12 +23186,24 @@ function executeClaudeCode(prompt, options) {
23169
23186
  }
23170
23187
  let proc;
23171
23188
  try {
23172
- proc = (0, import_child_process.spawn)(claudePath, args, {
23173
- cwd: (0, import_os.homedir)(),
23174
- env,
23175
- stdio: ["ignore", "pipe", "pipe"],
23176
- windowsHide: true
23177
- });
23189
+ const nodeScript = resolveNodeScript(claudePath);
23190
+ if (nodeScript) {
23191
+ proc = (0, import_child_process.spawn)(process.execPath, [nodeScript, ...args], {
23192
+ cwd: (0, import_os.homedir)(),
23193
+ env,
23194
+ stdio: ["ignore", "pipe", "pipe"],
23195
+ windowsHide: true
23196
+ });
23197
+ } else {
23198
+ proc = (0, import_child_process.spawn)(claudePath, args, {
23199
+ cwd: (0, import_os.homedir)(),
23200
+ env,
23201
+ stdio: ["ignore", "pipe", "pipe"],
23202
+ windowsHide: true,
23203
+ // Windows needs shell:true to execute .cmd batch wrappers from npm
23204
+ shell: process.platform === "win32"
23205
+ });
23206
+ }
23178
23207
  } catch (err) {
23179
23208
  resolve3({
23180
23209
  stdout: "",
@@ -23290,6 +23319,31 @@ function executeClaudeCode(prompt, options) {
23290
23319
  // src/shared/agent-executor.ts
23291
23320
  init_cloud_sync();
23292
23321
  var DEFAULT_HTTP_TIMEOUT_MS = 6e4;
23322
+ var _codexScriptResolved = false;
23323
+ var _codexNodeScript = null;
23324
+ function resolveCodexNodeScript() {
23325
+ if (_codexScriptResolved) return _codexNodeScript;
23326
+ _codexScriptResolved = true;
23327
+ if (process.platform !== "win32") return null;
23328
+ try {
23329
+ const cmdPath = (0, import_child_process2.execSync)("where codex.cmd", {
23330
+ encoding: "utf-8",
23331
+ timeout: 5e3,
23332
+ stdio: ["ignore", "pipe", "ignore"]
23333
+ }).trim().split("\n")[0].trim();
23334
+ if (!cmdPath) return null;
23335
+ const content = (0, import_fs3.readFileSync)(cmdPath, "utf-8");
23336
+ const match = content.match(/%dp0%\\(.+?\.js)/);
23337
+ if (!match) return null;
23338
+ const script = (0, import_path3.join)((0, import_path3.dirname)(cmdPath), match[1]);
23339
+ if ((0, import_fs3.existsSync)(script)) {
23340
+ _codexNodeScript = script;
23341
+ return script;
23342
+ }
23343
+ } catch {
23344
+ }
23345
+ return null;
23346
+ }
23293
23347
  async function executeAgent(options) {
23294
23348
  const model = options.model.trim();
23295
23349
  if (model.startsWith("ollama:")) {
@@ -23318,6 +23372,7 @@ async function executeClaude(options) {
23318
23372
  maxTurns: options.maxTurns,
23319
23373
  allowedTools: options.allowedTools,
23320
23374
  disallowedTools: options.disallowedTools,
23375
+ permissionMode: options.permissionMode,
23321
23376
  onProgress: options.onProgress,
23322
23377
  onConsoleLog: options.onConsoleLog,
23323
23378
  resumeSessionId: options.resumeSessionId,
@@ -23353,12 +23408,24 @@ async function executeCodex(options) {
23353
23408
  }
23354
23409
  let proc;
23355
23410
  try {
23356
- proc = (0, import_child_process2.spawn)("codex", args, {
23357
- cwd: (0, import_os4.homedir)(),
23358
- env: process.env,
23359
- stdio: ["ignore", "pipe", "pipe"],
23360
- windowsHide: true
23361
- });
23411
+ const nodeScript = resolveCodexNodeScript();
23412
+ if (nodeScript) {
23413
+ proc = (0, import_child_process2.spawn)(process.execPath, [nodeScript, ...args], {
23414
+ cwd: (0, import_os4.homedir)(),
23415
+ env: process.env,
23416
+ stdio: ["ignore", "pipe", "pipe"],
23417
+ windowsHide: true
23418
+ });
23419
+ } else {
23420
+ const codexCmd = process.platform === "win32" ? "codex.cmd" : "codex";
23421
+ proc = (0, import_child_process2.spawn)(codexCmd, args, {
23422
+ cwd: (0, import_os4.homedir)(),
23423
+ env: process.env,
23424
+ stdio: ["ignore", "pipe", "pipe"],
23425
+ windowsHide: true,
23426
+ shell: process.platform === "win32"
23427
+ });
23428
+ }
23362
23429
  } catch (err) {
23363
23430
  resolve3({
23364
23431
  output: `Error: Failed to spawn codex CLI: ${err instanceof Error ? err.message : String(err)}`,
@@ -23383,6 +23450,7 @@ async function executeCodex(options) {
23383
23450
  }
23384
23451
  return;
23385
23452
  }
23453
+ const onConsoleLog = options.onConsoleLog;
23386
23454
  proc.stdout.on("data", (data) => {
23387
23455
  const chunk = data.toString();
23388
23456
  stdout += chunk;
@@ -23390,9 +23458,18 @@ async function executeCodex(options) {
23390
23458
  const lines = buffer2.split("\n");
23391
23459
  buffer2 = lines.pop() ?? "";
23392
23460
  for (const line of lines) {
23393
- parseCodexEventLine(line, (nextSessionId, textChunk) => {
23394
- if (nextSessionId) sessionId = nextSessionId;
23395
- if (textChunk) outputParts.push(textChunk);
23461
+ parseCodexEventLine(line, (evt) => {
23462
+ if (evt.sessionId) sessionId = evt.sessionId;
23463
+ if (evt.text) {
23464
+ outputParts.push(evt.text);
23465
+ onConsoleLog?.({ entryType: "assistant_text", content: evt.text.slice(0, 2e3) });
23466
+ }
23467
+ if (evt.toolCall) {
23468
+ onConsoleLog?.({ entryType: "tool_call", content: `\u2192 ${evt.toolCall.tool}(${JSON.stringify(evt.toolCall.arguments).slice(0, 500)})` });
23469
+ }
23470
+ if (evt.toolResult) {
23471
+ onConsoleLog?.({ entryType: "tool_result", content: evt.toolResult });
23472
+ }
23396
23473
  });
23397
23474
  }
23398
23475
  });
@@ -23411,9 +23488,15 @@ async function executeCodex(options) {
23411
23488
  settled = true;
23412
23489
  clearTimeout(timer);
23413
23490
  if (buffer2.trim()) {
23414
- parseCodexEventLine(buffer2.trim(), (nextSessionId, textChunk) => {
23415
- if (nextSessionId) sessionId = nextSessionId;
23416
- if (textChunk) outputParts.push(textChunk);
23491
+ parseCodexEventLine(buffer2.trim(), (evt) => {
23492
+ if (evt.sessionId) sessionId = evt.sessionId;
23493
+ if (evt.text) {
23494
+ outputParts.push(evt.text);
23495
+ onConsoleLog?.({ entryType: "assistant_text", content: evt.text.slice(0, 2e3) });
23496
+ }
23497
+ if (evt.toolCall) {
23498
+ onConsoleLog?.({ entryType: "tool_call", content: `\u2192 ${evt.toolCall.tool}(${JSON.stringify(evt.toolCall.arguments).slice(0, 500)})` });
23499
+ }
23417
23500
  });
23418
23501
  }
23419
23502
  const output = outputParts.join("\n\n").trim() || stderr.trim() || stdout.trim() || "";
@@ -23764,7 +23847,7 @@ function parseCodexEventLine(line, onEvent) {
23764
23847
  }
23765
23848
  const type = parsed.type;
23766
23849
  if (type === "thread.started" && typeof parsed.thread_id === "string") {
23767
- onEvent(parsed.thread_id, null);
23850
+ onEvent({ sessionId: parsed.thread_id });
23768
23851
  return;
23769
23852
  }
23770
23853
  if (type === "item.completed") {
@@ -23772,7 +23855,19 @@ function parseCodexEventLine(line, onEvent) {
23772
23855
  if (!item) return;
23773
23856
  const itemType = item.type;
23774
23857
  if (itemType === "agent_message" && typeof item.text === "string") {
23775
- onEvent(null, item.text);
23858
+ onEvent({ text: item.text });
23859
+ } else if (itemType === "mcp_tool_call") {
23860
+ const tool = typeof item.tool === "string" ? item.tool : "unknown";
23861
+ const args = item.arguments ?? {};
23862
+ onEvent({ toolCall: { tool, arguments: args } });
23863
+ const result = item.result;
23864
+ if (result) {
23865
+ const content = result.content;
23866
+ if (Array.isArray(content)) {
23867
+ const text = content.map((c) => typeof c.text === "string" ? c.text : "").filter(Boolean).join("\n");
23868
+ if (text) onEvent({ toolResult: text.slice(0, 500) });
23869
+ }
23870
+ }
23776
23871
  }
23777
23872
  }
23778
23873
  }
@@ -24260,7 +24355,8 @@ async function getModelAuthStatus(db2, roomId, model) {
24260
24355
  envVar: null,
24261
24356
  hasCredential: false,
24262
24357
  hasEnvKey: false,
24263
- ready
24358
+ ready,
24359
+ maskedKey: null
24264
24360
  };
24265
24361
  }
24266
24362
  function resolveApiKeyForModel(db2, roomId, model) {
@@ -24279,6 +24375,7 @@ function resolveApiAuthStatus(db2, roomId, credentialName, envVar, provider) {
24279
24375
  const clerkCred = getClerkCredential(db2, credentialName);
24280
24376
  const envKey = getEnvValue(envVar);
24281
24377
  const hasCredential = Boolean(roomCred || sharedRoomCred || clerkCred);
24378
+ const activeKey = roomCred || sharedRoomCred || clerkCred || envKey || null;
24282
24379
  return {
24283
24380
  provider,
24284
24381
  mode: "api",
@@ -24286,9 +24383,16 @@ function resolveApiAuthStatus(db2, roomId, credentialName, envVar, provider) {
24286
24383
  envVar,
24287
24384
  hasCredential,
24288
24385
  hasEnvKey: Boolean(envKey),
24289
- ready: Boolean(hasCredential || envKey)
24386
+ ready: Boolean(hasCredential || envKey),
24387
+ maskedKey: maskKey(activeKey)
24290
24388
  };
24291
24389
  }
24390
+ function maskKey(key) {
24391
+ if (!key) return null;
24392
+ const trimmed = key.trim();
24393
+ if (trimmed.length <= 8) return `${trimmed.slice(0, 3)}...`;
24394
+ return `${trimmed.slice(0, 7)}...${trimmed.slice(-4)}`;
24395
+ }
24292
24396
  function resolveApiKey(db2, roomId, credentialName, envVar) {
24293
24397
  const roomCred = getRoomCredential(db2, roomId, credentialName);
24294
24398
  if (roomCred) return roomCred;
@@ -24331,8 +24435,9 @@ function getEnvValue(envVar) {
24331
24435
  return (process.env[envVar] || "").trim();
24332
24436
  }
24333
24437
  function checkCodexCliAvailable() {
24438
+ const cmd = process.platform === "win32" ? "codex.cmd" : "codex";
24334
24439
  try {
24335
- (0, import_node_child_process.execSync)("codex --version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] });
24440
+ (0, import_node_child_process.execSync)(`"${cmd}" --version`, { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] });
24336
24441
  return true;
24337
24442
  } catch {
24338
24443
  return false;
@@ -24373,6 +24478,8 @@ function createCycleLogBuffer(cycleId, writer, onEntry) {
24373
24478
 
24374
24479
  // src/shared/web-tools.ts
24375
24480
  var import_crypto6 = require("crypto");
24481
+ var import_node_os2 = require("node:os");
24482
+ var import_node_path2 = __toESM(require("node:path"));
24376
24483
  var MAX_CONTENT_CHARS = 12e3;
24377
24484
  var MAX_SNAPSHOT_CHARS = 8e3;
24378
24485
  var _browser = null;
@@ -24680,7 +24787,7 @@ ${text.slice(0, MAX_SNAPSHOT_CHARS)}`;
24680
24787
  await page.keyboard.type(action.value, { delay: 50 });
24681
24788
  break;
24682
24789
  case "screenshot": {
24683
- const tmpPath = `/tmp/quoroom-screenshot-${Date.now()}.png`;
24790
+ const tmpPath = import_node_path2.default.join((0, import_node_os2.tmpdir)(), `quoroom-screenshot-${Date.now()}.png`);
24684
24791
  await page.screenshot({ path: tmpPath, type: "png", fullPage: false });
24685
24792
  intermediateSnapshots.push(`[Screenshot saved \u2014 ${page.url()}]
24686
24793
  File: ${tmpPath}
@@ -25647,6 +25754,20 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
25647
25754
  );
25648
25755
  options?.onCycleLifecycle?.("created", cycle.id, roomId);
25649
25756
  try {
25757
+ const provider = getModelProvider(model);
25758
+ if (provider === "openai_api" || provider === "anthropic_api") {
25759
+ const apiKeyCheck = resolveApiKeyForModel(db2, roomId, model);
25760
+ if (!apiKeyCheck) {
25761
+ const label = provider === "openai_api" ? "OpenAI" : "Anthropic";
25762
+ const msg = `Missing ${label} API key. Set it in Room Settings or the Setup Guide.`;
25763
+ logBuffer2.addSynthetic("error", msg);
25764
+ logBuffer2.flush();
25765
+ completeWorkerCycle(db2, cycle.id, msg, void 0);
25766
+ options?.onCycleLifecycle?.("failed", cycle.id, roomId);
25767
+ updateAgentState(db2, worker.id, "idle");
25768
+ return msg;
25769
+ }
25770
+ }
25650
25771
  updateAgentState(db2, worker.id, "thinking");
25651
25772
  logBuffer2.addSynthetic("system", `Cycle started \u2014 observing room state...`);
25652
25773
  checkExpiredDecisions(db2);
@@ -25683,7 +25804,11 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
25683
25804
  }
25684
25805
  const skillContent = loadSkillsForAgent(db2, roomId, status2.room.goal ?? "");
25685
25806
  const rolePreset = worker.role ? WORKER_ROLE_PRESETS[worker.role] : void 0;
25807
+ const namePrefix = worker.name ? `Your name is ${worker.name}.
25808
+
25809
+ ` : "";
25686
25810
  const systemPrompt = [
25811
+ namePrefix,
25687
25812
  rolePreset?.systemPromptPrefix ? `${rolePreset.systemPromptPrefix}
25688
25813
 
25689
25814
  ` : "",
@@ -26042,6 +26167,8 @@ This is NOT optional \u2014 every cycle must produce at least one skill report.`
26042
26167
  onConsoleLog: logBuffer2.onConsoleLog,
26043
26168
  // CLI models: block non-quoroom MCP tools (daymon, etc.)
26044
26169
  disallowedTools: isCli ? "mcp__daymon*" : void 0,
26170
+ // CLI models: bypass permission prompts for headless operation
26171
+ permissionMode: isCli ? "bypassPermissions" : void 0,
26045
26172
  // CLI models: pass resumeSessionId for native --resume
26046
26173
  resumeSessionId,
26047
26174
  // API models: pass conversation history + persistence callback
@@ -26952,13 +27079,13 @@ var import_node_crypto5 = __toESM(require("node:crypto"));
26952
27079
 
26953
27080
  // src/server/runtime.ts
26954
27081
  var import_node_fs3 = require("node:fs");
26955
- var import_node_path3 = require("node:path");
26956
- var import_node_os2 = require("node:os");
27082
+ var import_node_path4 = require("node:path");
27083
+ var import_node_os3 = require("node:os");
26957
27084
  var import_node_cron = __toESM(require_node_cron());
26958
27085
 
26959
27086
  // src/shared/task-runner.ts
26960
- var import_path3 = require("path");
26961
- var import_fs3 = require("fs");
27087
+ var import_path4 = require("path");
27088
+ var import_fs4 = require("fs");
26962
27089
  init_cloud_sync();
26963
27090
 
26964
27091
  // src/shared/learned-context.ts
@@ -27387,6 +27514,7 @@ ${augmentedPrompt}`;
27387
27514
  maxTurns,
27388
27515
  allowedTools,
27389
27516
  disallowedTools,
27517
+ permissionMode: "bypassPermissions",
27390
27518
  onConsoleLog: consoleLog.onConsoleLog,
27391
27519
  onProgress: (progress) => {
27392
27520
  const now = Date.now();
@@ -27438,6 +27566,7 @@ ${retryPrompt}`;
27438
27566
  maxTurns,
27439
27567
  allowedTools,
27440
27568
  disallowedTools,
27569
+ permissionMode: "bypassPermissions",
27441
27570
  onConsoleLog: retryConsoleLog.onConsoleLog,
27442
27571
  onProgress: (progress) => {
27443
27572
  const now = Date.now();
@@ -27527,13 +27656,13 @@ function finishRun(db2, runId, taskId, task, result, resultsDir, onComplete, onF
27527
27656
  }
27528
27657
  }
27529
27658
  function saveResult(resultsDir, taskName, output, result) {
27530
- if (!(0, import_fs3.existsSync)(resultsDir)) {
27531
- (0, import_fs3.mkdirSync)(resultsDir, { recursive: true });
27659
+ if (!(0, import_fs4.existsSync)(resultsDir)) {
27660
+ (0, import_fs4.mkdirSync)(resultsDir, { recursive: true });
27532
27661
  }
27533
27662
  const safeName = taskName.replace(/[^a-zA-Z0-9-_]/g, "_").substring(0, 50);
27534
27663
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
27535
27664
  const fileName = `${safeName}-${timestamp}.md`;
27536
- const filePath = (0, import_path3.join)(resultsDir, fileName);
27665
+ const filePath = (0, import_path4.join)(resultsDir, fileName);
27537
27666
  const markdown = `# Task: ${taskName}
27538
27667
 
27539
27668
  **Date:** ${(/* @__PURE__ */ new Date()).toLocaleString()}
@@ -27544,14 +27673,14 @@ function saveResult(resultsDir, taskName, output, result) {
27544
27673
 
27545
27674
  ${output}
27546
27675
  `;
27547
- (0, import_fs3.writeFileSync)(filePath, markdown, "utf-8");
27676
+ (0, import_fs4.writeFileSync)(filePath, markdown, "utf-8");
27548
27677
  return filePath;
27549
27678
  }
27550
27679
 
27551
27680
  // src/server/clerk-profile.ts
27552
27681
  var import_node_crypto3 = require("node:crypto");
27553
27682
  var import_node_fs2 = require("node:fs");
27554
- var import_node_path2 = require("node:path");
27683
+ var import_node_path3 = require("node:path");
27555
27684
 
27556
27685
  // src/server/provider-cli.ts
27557
27686
  var import_node_child_process2 = require("node:child_process");
@@ -27561,7 +27690,9 @@ function getProviderCliCommand(provider, platform = process.platform) {
27561
27690
  }
27562
27691
  function safeExec(cmd, args) {
27563
27692
  try {
27564
- const stdout = (0, import_node_child_process2.execFileSync)(cmd, args, { timeout: CLI_PROBE_TIMEOUT_MS, stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
27693
+ const opts = { timeout: CLI_PROBE_TIMEOUT_MS, stdio: ["ignore", "pipe", "pipe"] };
27694
+ if (process.platform === "win32") opts.shell = true;
27695
+ const stdout = (0, import_node_child_process2.execFileSync)(cmd, args, opts).toString().trim();
27565
27696
  return { ok: true, stdout, stderr: "" };
27566
27697
  } catch (err) {
27567
27698
  const e = err;
@@ -27616,7 +27747,7 @@ function findAnyRoomCredential2(db2, credentialName) {
27616
27747
  }
27617
27748
  return null;
27618
27749
  }
27619
- function maskKey(key) {
27750
+ function maskKey2(key) {
27620
27751
  if (!key) return null;
27621
27752
  const trimmed = key.trim();
27622
27753
  if (trimmed.length <= 8) return `${trimmed.slice(0, 3)}...`;
@@ -27634,7 +27765,7 @@ function getClerkApiAuthState(db2, provider) {
27634
27765
  hasSavedKey: Boolean(savedKey),
27635
27766
  hasEnvKey: Boolean(envKey),
27636
27767
  ready: Boolean(activeKey),
27637
- maskedKey: maskKey(activeKey)
27768
+ maskedKey: maskKey2(activeKey)
27638
27769
  };
27639
27770
  }
27640
27771
  function getClerkApiAuth(db2) {
@@ -27671,7 +27802,7 @@ function sha2564(value) {
27671
27802
  return (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
27672
27803
  }
27673
27804
  function readProjectFile(relPath) {
27674
- const abs = (0, import_node_path2.resolve)(process.cwd(), relPath);
27805
+ const abs = (0, import_node_path3.resolve)(process.cwd(), relPath);
27675
27806
  if (!(0, import_node_fs2.existsSync)(abs)) return null;
27676
27807
  try {
27677
27808
  return (0, import_node_fs2.readFileSync)(abs, "utf8");
@@ -27923,9 +28054,9 @@ var lastAssistantReplyAt = 0;
27923
28054
  var dbRef = null;
27924
28055
  var generating = false;
27925
28056
  var lastCommentary = "";
27926
- var lastFormatMode = null;
27927
28057
  var commentaryCount = 0;
27928
28058
  var roomNameCache = /* @__PURE__ */ new Map();
28059
+ var queenNicknameCache = /* @__PURE__ */ new Map();
27929
28060
  var workerNameCache = /* @__PURE__ */ new Map();
27930
28061
  var cycleWorkerCache = /* @__PURE__ */ new Map();
27931
28062
  function getRoomName(db2, roomId) {
@@ -27933,8 +28064,16 @@ function getRoomName(db2, roomId) {
27933
28064
  const room = getRoom(db2, roomId);
27934
28065
  const name = room?.name ?? `Room #${roomId}`;
27935
28066
  roomNameCache.set(roomId, name);
28067
+ if (room?.queenNickname) queenNicknameCache.set(roomId, room.queenNickname);
27936
28068
  return name;
27937
28069
  }
28070
+ function getQueenNickname(db2, roomId) {
28071
+ if (queenNicknameCache.has(roomId)) return queenNicknameCache.get(roomId);
28072
+ const room = getRoom(db2, roomId);
28073
+ const nick = room?.queenNickname ?? "";
28074
+ if (nick) queenNicknameCache.set(roomId, nick);
28075
+ return nick;
28076
+ }
27938
28077
  function getWorkerName(db2, workerId) {
27939
28078
  if (workerNameCache.has(workerId)) return workerNameCache.get(workerId);
27940
28079
  const worker = getWorker(db2, workerId);
@@ -27946,17 +28085,17 @@ function normalizeRoomLabel(name) {
27946
28085
  const normalized = (name || "").toLowerCase().replace(/[^a-z0-9]/g, "");
27947
28086
  return normalized || "room";
27948
28087
  }
27949
- function normalizeActorLabel(workerName, roomName) {
28088
+ function normalizeActorLabel(workerName, roomName, queenNickname) {
27950
28089
  const raw = (workerName || "").trim();
27951
- if (!raw) return "queen";
28090
+ if (!raw) return queenNickname?.toLowerCase() || "queen";
27952
28091
  const lower = raw.toLowerCase();
27953
28092
  const compact = lower.replace(/[^a-z0-9]/g, "");
27954
28093
  const roomCompact = normalizeRoomLabel(roomName);
27955
28094
  if (lower === "queen" || lower.endsWith(" queen") || lower.endsWith("_queen") || lower.endsWith("-queen") || compact === `${roomCompact}queen` || compact === roomCompact) {
27956
- return "queen";
28095
+ return queenNickname?.toLowerCase() || "queen";
27957
28096
  }
27958
28097
  const actor = compact.replace(/queen$/, "");
27959
- if (!actor || actor === roomCompact) return "queen";
28098
+ if (!actor || actor === roomCompact) return queenNickname?.toLowerCase() || "queen";
27960
28099
  return actor;
27961
28100
  }
27962
28101
  function getCommentaryHoldCount(db2) {
@@ -28104,7 +28243,7 @@ function sanitizeContent(text) {
28104
28243
  return text.replace(/mcp__\w+__(\w+)/g, (_, name) => friendlyName(name)).replace(/\bquoroom_(\w+)/g, (_, name) => friendlyName(`quoroom_${name}`));
28105
28244
  }
28106
28245
  function normalizeCommentaryOutput(text) {
28107
- return text.replace(/\*\*[^*\n]*\bqueen\b[^*\n]*\*\*/gi, "**queen**").replace(/\b[A-Za-z][A-Za-z0-9 ]{0,50}\s+Queen\b/g, "queen").replace(/\bin\s+`([^`\n]+)`/gi, (_full, room) => `in \`${normalizeRoomLabel(String(room))}\``);
28246
+ return text.replace(/\bin\s+`([^`\n]+)`/gi, (_full, room) => `in \`${normalizeRoomLabel(String(room))}\``);
28108
28247
  }
28109
28248
  function isKeeperInputEcho(content) {
28110
28249
  const lower = content.toLowerCase();
@@ -28118,37 +28257,35 @@ function formatRawLogs(entries) {
28118
28257
  if (actionable.length === 0) return "";
28119
28258
  const workers = /* @__PURE__ */ new Map();
28120
28259
  for (const entry of actionable) {
28121
- const roomLabel = normalizeRoomLabel(entry.roomName);
28122
- const who = normalizeActorLabel(entry.workerName, roomLabel);
28123
- const key = `${entry.roomId}:${who}`;
28124
- if (!workers.has(key)) workers.set(key, { roomName: roomLabel, entries: [] });
28260
+ const workerLabel = entry.workerName && !entry.workerName.toLowerCase().includes("queen") ? entry.workerName.toLowerCase().replace(/\s+/g, "-") : "queen";
28261
+ const key = `${entry.roomId}:${workerLabel}`;
28262
+ if (!workers.has(key)) workers.set(key, { roomName: entry.roomName, roomId: entry.roomId, workerLabel, entries: [] });
28125
28263
  workers.get(key).entries.push(entry);
28126
28264
  }
28127
28265
  const sorted = [...workers.entries()].sort(([, a], [, b]) => {
28128
- const aIsQueen = (a.entries[0]?.workerName || "queen") === "queen";
28129
- const bIsQueen = (b.entries[0]?.workerName || "queen") === "queen";
28130
- if (aIsQueen && !bIsQueen) return 1;
28131
- if (!aIsQueen && bIsQueen) return -1;
28266
+ if (a.workerLabel === "queen" && b.workerLabel !== "queen") return 1;
28267
+ if (a.workerLabel !== "queen" && b.workerLabel === "queen") return -1;
28132
28268
  return b.entries.length - a.entries.length;
28133
28269
  });
28134
28270
  const lines = [];
28135
- for (const [, { roomName, entries: wEntries }] of sorted) {
28136
- const who = normalizeActorLabel(wEntries[0]?.workerName || "", roomName);
28137
- const label = who === "queen" ? `queen in \`${roomName}\`` : `${who} in \`${roomName}\``;
28138
- lines.push(`[${label}]`);
28271
+ for (const [, { roomName, workerLabel, entries: wEntries }] of sorted) {
28272
+ const steps = wEntries.map((e) => e.seq).filter((s) => s > 0);
28273
+ const stepRange = steps.length > 0 ? ` (Steps ${Math.min(...steps)}-${Math.max(...steps)})` : "";
28274
+ lines.push(`[${workerLabel}${stepRange}, "${roomName}"]`);
28139
28275
  for (const entry of wEntries) {
28276
+ const stepTag = entry.seq > 0 ? `Step ${entry.seq}: ` : "";
28140
28277
  switch (entry.entryType) {
28141
28278
  case "tool_call":
28142
- lines.push(` \u2192 ${humanizeToolCall(entry.content)}`);
28279
+ lines.push(` \u2192 ${stepTag}${humanizeToolCall(entry.content)}`);
28143
28280
  break;
28144
28281
  case "tool_result":
28145
- lines.push(` \u2190 ${sanitizeContent(entry.content.slice(0, 400))}`);
28282
+ lines.push(` \u2190 ${stepTag}${sanitizeContent(entry.content.slice(0, 400))}`);
28146
28283
  break;
28147
28284
  case "result":
28148
- lines.push(` OUTCOME: ${sanitizeContent(entry.content.slice(0, 600))}`);
28285
+ lines.push(` OUTCOME: ${stepTag}${sanitizeContent(entry.content.slice(0, 600))}`);
28149
28286
  break;
28150
28287
  case "error":
28151
- lines.push(` ERROR: ${sanitizeContent(entry.content.slice(0, 200))}`);
28288
+ lines.push(` ERROR: ${stepTag}${sanitizeContent(entry.content.slice(0, 200))}`);
28152
28289
  break;
28153
28290
  }
28154
28291
  }
@@ -28172,7 +28309,9 @@ function formatDirectCommentary(entries) {
28172
28309
  });
28173
28310
  for (const [worker, wEntries] of sorted) {
28174
28311
  const roomName = normalizeRoomLabel(wEntries[0]?.roomName || "");
28175
- const actor = normalizeActorLabel(worker, roomName);
28312
+ const roomId = wEntries[0]?.roomId ?? 0;
28313
+ const nick = dbRef && roomId ? getQueenNickname(dbRef, roomId) : "";
28314
+ const actor = normalizeActorLabel(worker, roomName, nick);
28176
28315
  const parts = [];
28177
28316
  for (const entry of wEntries) {
28178
28317
  switch (entry.entryType) {
@@ -28219,51 +28358,17 @@ function emitRoomCommentaryEvents(entries, commentary) {
28219
28358
  });
28220
28359
  }
28221
28360
  }
28222
- function countActorSections(rawLogs) {
28223
- const matches = rawLogs.match(/^\[[^\n]+\]$/gm);
28224
- return matches?.length ?? 0;
28225
- }
28226
- function pickFormatMode(rawLogs) {
28227
- const nonBulletModes = ["cinematic", "scoreboard", "timeline"];
28228
- const defaultMode = nonBulletModes[commentaryCount % nonBulletModes.length];
28229
- let selected = defaultMode;
28230
- if (selected === lastFormatMode) {
28231
- const idx = nonBulletModes.indexOf(defaultMode);
28232
- selected = nonBulletModes[(idx + 1) % nonBulletModes.length];
28233
- }
28234
- const actorCount = countActorSections(rawLogs);
28235
- const allowBullet = actorCount >= 3 && commentaryCount > 0 && commentaryCount % 4 === 0 && lastFormatMode !== "bullet";
28236
- return allowBullet ? "bullet" : selected;
28237
- }
28238
- function formatModeInstruction(mode) {
28239
- switch (mode) {
28240
- case "cinematic":
28241
- return "FORMAT MODE FOR THIS UPDATE: Cinematic narrative. No bullet list. Use 4-7 short lines with strong flow and clear turns.";
28242
- case "scoreboard":
28243
- return "FORMAT MODE FOR THIS UPDATE: Scoreboard snapshot. No bullet list. Use a short uppercase header, then compact metric-like lines, then verdict.";
28244
- case "timeline":
28245
- return "FORMAT MODE FOR THIS UPDATE: Play-by-play timeline. No bullet list. Use sequence-style lines that progress step-by-step.";
28246
- case "bullet":
28247
- return "FORMAT MODE FOR THIS UPDATE: Bullet board allowed. Keep to max 4 bullets, each meaningful. Add opener and closer lines.";
28248
- }
28249
- }
28250
28361
  async function generateCommentary(rawLogs) {
28251
28362
  if (!dbRef) return null;
28252
- const formatMode = pickFormatMode(rawLogs);
28253
28363
  const contextNote = lastCommentary ? `
28254
28364
 
28255
- --- Your previous update (DO NOT reuse the same opener, phrases, or sentence structure) ---
28365
+ --- Your previous update (DON'T repeat same opener or phrases) ---
28256
28366
  ${lastCommentary.slice(0, 400)}` : "";
28257
- const antiRepeatNote = lastFormatMode ? `
28258
- Previous format mode used: ${lastFormatMode}. Do NOT use it again now.` : "";
28259
- const modeInstruction = formatModeInstruction(formatMode);
28260
28367
  const prompt = `Here are the latest agent activity logs:
28261
28368
 
28262
- ${rawLogs}${contextNote}${antiRepeatNote}
28263
-
28264
- ${modeInstruction}
28369
+ ${rawLogs}${contextNote}
28265
28370
 
28266
- Write your live commentary as the Clerk. Keep every sentence on its own line. Vary rhythm and sentence length. Avoid template repetition. Bullet points are allowed only when explicitly requested by the mode instruction.`;
28371
+ Write your live commentary. Start with a bold **STATUS UPDATE** or **INCREDIBLE PROGRESS** header. Give each active worker their own narrative paragraph with bold name, step range, room name in quotes. Use flowing connected sentences, not one-per-line. Bold key actions, \`code\` for emails/URLs/domains. End with keeper analysis or score summary.`;
28267
28372
  const preferredModel = getSetting(dbRef, "clerk_model") || DEFAULT_CLERK_MODEL;
28268
28373
  const result = await executeClerkWithFallback({
28269
28374
  db: dbRef,
@@ -28296,7 +28401,6 @@ Write your live commentary as the Clerk. Keep every sentence on its own line. Va
28296
28401
  attempts: result.attempts.length + 1
28297
28402
  };
28298
28403
  }
28299
- lastFormatMode = formatMode;
28300
28404
  commentaryCount += 1;
28301
28405
  return {
28302
28406
  commentary: normalizeCommentaryOutput(boldCaps(result.output.trim())),
@@ -28579,9 +28683,9 @@ function stopCommentaryEngine() {
28579
28683
  generating = false;
28580
28684
  lastAssistantReplyAt = 0;
28581
28685
  lastCommentary = "";
28582
- lastFormatMode = null;
28583
28686
  commentaryCount = 0;
28584
28687
  roomNameCache.clear();
28688
+ queenNicknameCache.clear();
28585
28689
  workerNameCache.clear();
28586
28690
  cycleWorkerCache.clear();
28587
28691
  }
@@ -29608,7 +29712,8 @@ function buildClerkContext(db2, projectDocsSnapshot) {
29608
29712
  for (const room of activeRooms) {
29609
29713
  const goals = listGoals(db2, room.id).filter((g) => g.status === "active" || g.status === "in_progress");
29610
29714
  const workers = listRoomWorkers(db2, room.id);
29611
- parts.push(`- **${room.name}** (id:${room.id}, status:${room.status}, model:${room.workerModel})`);
29715
+ const queenLabel = room.queenNickname ? `, queen: ${room.queenNickname}` : "";
29716
+ parts.push(`- **${room.name}** (id:${room.id}, status:${room.status}, model:${room.workerModel}${queenLabel})`);
29612
29717
  if (room.goal) parts.push(` Objective: ${room.goal}`);
29613
29718
  if (goals.length > 0) parts.push(` Goals: ${goals.map((g) => `${g.description} (${Math.round(g.progress * 100)}%)`).join(", ")}`);
29614
29719
  if (workers.length > 0) parts.push(` Workers: ${workers.map((w) => w.name).join(", ")}`);
@@ -31015,7 +31120,7 @@ function queueClerkAlertRelay(db2) {
31015
31120
  });
31016
31121
  }
31017
31122
  function getResultsDir() {
31018
- return process.env.QUOROOM_RESULTS_DIR || (0, import_node_path3.join)((0, import_node_os2.homedir)(), APP_NAME, "results");
31123
+ return process.env.QUOROOM_RESULTS_DIR || (0, import_node_path4.join)((0, import_node_os3.homedir)(), APP_NAME, "results");
31019
31124
  }
31020
31125
  function queueTaskExecution(db2, taskId, options) {
31021
31126
  const task = getTask(db2, taskId);
@@ -31253,7 +31358,7 @@ function startWatch(db2, watch) {
31253
31358
  })();
31254
31359
  let watcher;
31255
31360
  const onChange = (eventType, filename) => {
31256
- const changed = filename ? (0, import_node_path3.join)(watch.path, filename.toString()) : watch.path;
31361
+ const changed = filename ? (0, import_node_path4.join)(watch.path, filename.toString()) : watch.path;
31257
31362
  scheduleWatchExecution(db2, watch, eventType, changed);
31258
31363
  };
31259
31364
  try {
@@ -31720,18 +31825,18 @@ function registerSkillRoutes(router) {
31720
31825
 
31721
31826
  // src/shared/watch-path.ts
31722
31827
  var import_os5 = require("os");
31723
- var import_path4 = require("path");
31724
- var import_fs4 = require("fs");
31828
+ var import_path5 = require("path");
31829
+ var import_fs5 = require("fs");
31725
31830
  var SENSITIVE_HOME_SUFFIXES = [
31726
- `${import_path4.sep}.ssh`,
31727
- `${import_path4.sep}.gnupg`,
31728
- `${import_path4.sep}.aws`,
31729
- `${import_path4.sep}.env`,
31730
- `${import_path4.sep}.kube`,
31731
- `${import_path4.sep}.docker`,
31732
- `${import_path4.sep}.npmrc`,
31733
- `${import_path4.sep}.config${import_path4.sep}gh`,
31734
- `${import_path4.sep}Library${import_path4.sep}Keychains`
31831
+ `${import_path5.sep}.ssh`,
31832
+ `${import_path5.sep}.gnupg`,
31833
+ `${import_path5.sep}.aws`,
31834
+ `${import_path5.sep}.env`,
31835
+ `${import_path5.sep}.kube`,
31836
+ `${import_path5.sep}.docker`,
31837
+ `${import_path5.sep}.npmrc`,
31838
+ `${import_path5.sep}.config${import_path5.sep}gh`,
31839
+ `${import_path5.sep}Library${import_path5.sep}Keychains`
31735
31840
  ];
31736
31841
  function getTempRoots() {
31737
31842
  const roots = [(0, import_os5.tmpdir)()];
@@ -31739,13 +31844,13 @@ function getTempRoots() {
31739
31844
  return roots;
31740
31845
  }
31741
31846
  function validateWatchPath(watchPath) {
31742
- const resolved = (0, import_path4.resolve)(watchPath);
31743
- if (!(0, import_path4.isAbsolute)(resolved)) {
31847
+ const resolved = (0, import_path5.resolve)(watchPath);
31848
+ if (!(0, import_path5.isAbsolute)(resolved)) {
31744
31849
  return "Path must be absolute.";
31745
31850
  }
31746
31851
  let realPath;
31747
31852
  try {
31748
- realPath = (0, import_fs4.realpathSync)(resolved);
31853
+ realPath = (0, import_fs5.realpathSync)(resolved);
31749
31854
  } catch {
31750
31855
  realPath = resolved;
31751
31856
  }
@@ -31993,15 +32098,27 @@ function registerChatRoutes(router) {
31993
32098
  insertChatMessage(ctx.db, roomId, "user", message);
31994
32099
  const model = queen.model ?? "claude";
31995
32100
  const apiKey = resolveApiKeyForModel(ctx.db, roomId, model);
32101
+ const namePrefix = room.queenNickname ? `Your name is ${room.queenNickname}. ` : "";
32102
+ const chatSystemPrompt = `${namePrefix}You are the queen of the "${room.name}" room. The keeper (your human) is chatting with you.
32103
+
32104
+ RULES FOR CHAT:
32105
+ - Be concise \u2014 2-4 sentences unless the keeper asks for detail.
32106
+ - Answer what was asked. Do NOT dump a full status report unless requested.
32107
+ - Do NOT run tools or take autonomous actions unless the keeper asks you to.
32108
+ - Be friendly and natural, not formal or robotic.
32109
+ - If the keeper greets you, greet back briefly and ask what they need.
32110
+
32111
+ You know your room's context: goal is "${room.goal || "not set yet"}".`;
31996
32112
  const result = await executeAgent({
31997
32113
  model,
31998
32114
  prompt: message,
31999
- systemPrompt: queen.systemPrompt,
32115
+ systemPrompt: chatSystemPrompt,
32000
32116
  resumeSessionId: room.chatSessionId ?? void 0,
32001
32117
  apiKey,
32002
32118
  maxTurns: 10,
32003
- timeoutMs: 3 * 60 * 1e3
32119
+ timeoutMs: 3 * 60 * 1e3,
32004
32120
  // 3 minutes
32121
+ permissionMode: "bypassPermissions"
32005
32122
  });
32006
32123
  if (result.exitCode !== 0 || result.timedOut) {
32007
32124
  const rawOutput = result.output?.trim();
@@ -32033,15 +32150,15 @@ function registerChatRoutes(router) {
32033
32150
  }
32034
32151
 
32035
32152
  // src/server/routes/status.ts
32036
- var import_node_os4 = __toESM(require("node:os"));
32153
+ var import_node_os5 = __toESM(require("node:os"));
32037
32154
  var import_node_child_process3 = require("node:child_process");
32038
32155
  var import_node_util = require("node:util");
32039
32156
 
32040
32157
  // src/server/db.ts
32041
32158
  var import_better_sqlite3 = __toESM(require("better-sqlite3"));
32042
32159
  var import_os6 = require("os");
32043
- var import_path5 = require("path");
32044
- var import_fs5 = require("fs");
32160
+ var import_path6 = require("path");
32161
+ var import_fs6 = require("fs");
32045
32162
 
32046
32163
  // src/shared/db-migrations.ts
32047
32164
  var import_crypto10 = require("crypto");
@@ -32653,7 +32770,7 @@ function expandTilde(p) {
32653
32770
  return p;
32654
32771
  }
32655
32772
  function getDefaultDataDir() {
32656
- return (0, import_path5.join)((0, import_os6.homedir)(), ".quoroom");
32773
+ return (0, import_path6.join)((0, import_os6.homedir)(), ".quoroom");
32657
32774
  }
32658
32775
  function getDataDir() {
32659
32776
  const raw = process.env.QUOROOM_DATA_DIR || getDefaultDataDir();
@@ -32662,8 +32779,8 @@ function getDataDir() {
32662
32779
  function getServerDatabase() {
32663
32780
  if (db) return db;
32664
32781
  const dataDir = getDataDir();
32665
- (0, import_fs5.mkdirSync)(dataDir, { recursive: true });
32666
- const rawPath = process.env.QUOROOM_DB_PATH || (0, import_path5.join)(dataDir, "data.db");
32782
+ (0, import_fs6.mkdirSync)(dataDir, { recursive: true });
32783
+ const rawPath = process.env.QUOROOM_DB_PATH || (0, import_path6.join)(dataDir, "data.db");
32667
32784
  const dbPath = expandTilde(rawPath);
32668
32785
  db = new import_better_sqlite3.default(dbPath);
32669
32786
  db.pragma("journal_mode = WAL");
@@ -32689,17 +32806,17 @@ var import_node_https2 = __toESM(require("node:https"));
32689
32806
 
32690
32807
  // src/server/autoUpdate.ts
32691
32808
  var import_node_fs4 = __toESM(require("node:fs"));
32692
- var import_node_path4 = __toESM(require("node:path"));
32693
- var import_node_os3 = require("node:os");
32809
+ var import_node_path5 = __toESM(require("node:path"));
32810
+ var import_node_os4 = require("node:os");
32694
32811
  var import_node_https = __toESM(require("node:https"));
32695
32812
  var import_node_http = __toESM(require("node:http"));
32696
32813
  var import_node_crypto7 = require("node:crypto");
32697
32814
  var import_promises = require("node:stream/promises");
32698
- var USER_APP_DIR = import_node_path4.default.join((0, import_node_os3.homedir)(), ".quoroom", "app");
32699
- var STAGING_DIR = import_node_path4.default.join((0, import_node_os3.homedir)(), ".quoroom", "app-staging");
32700
- var BOOT_MARKER = import_node_path4.default.join(USER_APP_DIR, ".booting");
32701
- var CRASH_COUNT_FILE = import_node_path4.default.join(USER_APP_DIR, ".crash_count");
32702
- var VERSION_FILE = import_node_path4.default.join(USER_APP_DIR, "version.json");
32815
+ var USER_APP_DIR = import_node_path5.default.join((0, import_node_os4.homedir)(), ".quoroom", "app");
32816
+ var STAGING_DIR = import_node_path5.default.join((0, import_node_os4.homedir)(), ".quoroom", "app-staging");
32817
+ var BOOT_MARKER = import_node_path5.default.join(USER_APP_DIR, ".booting");
32818
+ var CRASH_COUNT_FILE = import_node_path5.default.join(USER_APP_DIR, ".crash_count");
32819
+ var VERSION_FILE = import_node_path5.default.join(USER_APP_DIR, "version.json");
32703
32820
  var status = { state: "idle" };
32704
32821
  var downloadInProgress = false;
32705
32822
  function getAutoUpdateStatus() {
@@ -32777,7 +32894,7 @@ function sha256File(filePath) {
32777
32894
  async function downloadAndExtract(bundleUrl) {
32778
32895
  import_node_fs4.default.rmSync(STAGING_DIR, { recursive: true, force: true });
32779
32896
  import_node_fs4.default.mkdirSync(STAGING_DIR, { recursive: true });
32780
- const tarballPath = import_node_path4.default.join(STAGING_DIR, "update.tar.gz");
32897
+ const tarballPath = import_node_path5.default.join(STAGING_DIR, "update.tar.gz");
32781
32898
  const response = await followRedirects(bundleUrl);
32782
32899
  const fileStream = import_node_fs4.default.createWriteStream(tarballPath);
32783
32900
  await (0, import_promises.pipeline)(response, fileStream);
@@ -32785,11 +32902,17 @@ async function downloadAndExtract(bundleUrl) {
32785
32902
  import_node_fs4.default.unlinkSync(tarballPath);
32786
32903
  }
32787
32904
  async function extractTarGz(tarballPath, destDir) {
32788
- const { execSync: execSync5 } = await import("node:child_process");
32789
- execSync5(`tar xzf ${JSON.stringify(tarballPath)} -C ${JSON.stringify(destDir)}`, { stdio: "ignore" });
32905
+ const { execSync: execSync6 } = await import("node:child_process");
32906
+ try {
32907
+ execSync6(`tar xzf ${JSON.stringify(tarballPath)} -C ${JSON.stringify(destDir)}`, { stdio: "ignore" });
32908
+ } catch (err) {
32909
+ throw new Error(
32910
+ `Failed to extract update bundle. ` + (process.platform === "win32" ? "Ensure Windows 10 build 17063+ (tar.exe required)." : "System tar command failed.") + ` ${err instanceof Error ? err.message : String(err)}`
32911
+ );
32912
+ }
32790
32913
  }
32791
32914
  async function verifyUpdate(dir) {
32792
- const versionPath = import_node_path4.default.join(dir, "version.json");
32915
+ const versionPath = import_node_path5.default.join(dir, "version.json");
32793
32916
  if (!import_node_fs4.default.existsSync(versionPath)) {
32794
32917
  throw new Error("Missing version.json in update bundle");
32795
32918
  }
@@ -32799,7 +32922,7 @@ async function verifyUpdate(dir) {
32799
32922
  }
32800
32923
  if (info.checksums) {
32801
32924
  for (const [relativePath, expectedHash] of Object.entries(info.checksums)) {
32802
- const filePath = import_node_path4.default.join(dir, relativePath);
32925
+ const filePath = import_node_path5.default.join(dir, relativePath);
32803
32926
  if (!import_node_fs4.default.existsSync(filePath)) {
32804
32927
  throw new Error(`Missing file in update: ${relativePath}`);
32805
32928
  }
@@ -32811,7 +32934,7 @@ async function verifyUpdate(dir) {
32811
32934
  }
32812
32935
  const requiredFiles = ["lib/cli.js", "lib/api-server.js", "lib/server.js"];
32813
32936
  for (const f of requiredFiles) {
32814
- if (!import_node_fs4.default.existsSync(import_node_path4.default.join(dir, f))) {
32937
+ if (!import_node_fs4.default.existsSync(import_node_path5.default.join(dir, f))) {
32815
32938
  throw new Error(`Missing required file: ${f}`);
32816
32939
  }
32817
32940
  }
@@ -32834,7 +32957,7 @@ function semverGt(a, b) {
32834
32957
  }
32835
32958
  function getCurrentVersion() {
32836
32959
  try {
32837
- return true ? "0.1.26" : null.version;
32960
+ return true ? "0.1.27" : null.version;
32838
32961
  } catch {
32839
32962
  return "0.0.0";
32840
32963
  }
@@ -32853,6 +32976,8 @@ function getReadyUpdateVersion() {
32853
32976
  }
32854
32977
  async function checkAndApplyUpdate(bundleUrl, targetVersion) {
32855
32978
  if (downloadInProgress) return;
32979
+ const currentVersion = getCurrentVersion();
32980
+ if (!semverGt(targetVersion, currentVersion)) return;
32856
32981
  const readyVersion = getReadyUpdateVersion();
32857
32982
  if (readyVersion && !semverGt(targetVersion, readyVersion)) return;
32858
32983
  downloadInProgress = true;
@@ -32864,9 +32989,9 @@ async function checkAndApplyUpdate(bundleUrl, targetVersion) {
32864
32989
  status = { state: "verifying", version: targetVersion };
32865
32990
  const info = await verifyUpdate(STAGING_DIR);
32866
32991
  if (info.minEngineVersion) {
32867
- const currentVersion = getCurrentVersion();
32868
- if (semverGt(info.minEngineVersion, currentVersion)) {
32869
- console.error(`[auto-update] Update requires engine >= ${info.minEngineVersion}, current is ${currentVersion}. Skipping.`);
32992
+ const currentVersion2 = getCurrentVersion();
32993
+ if (semverGt(info.minEngineVersion, currentVersion2)) {
32994
+ console.error(`[auto-update] Update requires engine >= ${info.minEngineVersion}, current is ${currentVersion2}. Skipping.`);
32870
32995
  import_node_fs4.default.rmSync(STAGING_DIR, { recursive: true, force: true });
32871
32996
  status = { state: "error", error: `Requires full installer (engine >= ${info.minEngineVersion})` };
32872
32997
  return;
@@ -32991,7 +33116,7 @@ var cachedVersion = null;
32991
33116
  function getVersion3() {
32992
33117
  if (cachedVersion) return cachedVersion;
32993
33118
  try {
32994
- cachedVersion = true ? "0.1.26" : null.version;
33119
+ cachedVersion = true ? "0.1.27" : null.version;
32995
33120
  } catch {
32996
33121
  cachedVersion = "unknown";
32997
33122
  }
@@ -33062,11 +33187,11 @@ function warmStatusCaches() {
33062
33187
  }
33063
33188
  warmStatusCaches();
33064
33189
  function getResources() {
33065
- const [load1, load5] = import_node_os4.default.loadavg();
33066
- const total = import_node_os4.default.totalmem();
33067
- const free = import_node_os4.default.freemem();
33190
+ const [load1, load5] = import_node_os5.default.loadavg();
33191
+ const total = import_node_os5.default.totalmem();
33192
+ const free = import_node_os5.default.freemem();
33068
33193
  return {
33069
- cpuCount: import_node_os4.default.cpus().length,
33194
+ cpuCount: import_node_os5.default.cpus().length,
33070
33195
  loadAvg1m: Math.round(load1 * 100) / 100,
33071
33196
  loadAvg5m: Math.round(load5 * 100) / 100,
33072
33197
  memTotalGb: Math.round(total / 1024 / 1024 / 1024 * 10) / 10,
@@ -33926,7 +34051,9 @@ function startProviderAuthSession(provider) {
33926
34051
  const displayCommand = [cmd.command, ...cmd.args].join(" ");
33927
34052
  const child = (0, import_node_child_process4.spawn)(cmd.command, cmd.args, {
33928
34053
  stdio: ["pipe", "pipe", "pipe"],
33929
- env: { ...process.env, CI: "1", FORCE_COLOR: "0" }
34054
+ env: { ...process.env, CI: "1", FORCE_COLOR: "0" },
34055
+ // Windows needs shell:true to execute .cmd batch wrappers (claude.cmd, codex.cmd)
34056
+ shell: process.platform === "win32"
33930
34057
  });
33931
34058
  const startedAt2 = nowIso();
33932
34059
  const session = {
@@ -33998,7 +34125,7 @@ function startProviderAuthSession(provider) {
33998
34125
  // src/server/provider-install.ts
33999
34126
  var import_node_child_process5 = require("node:child_process");
34000
34127
  var import_node_crypto10 = require("node:crypto");
34001
- var import_node_path5 = __toESM(require("node:path"));
34128
+ var import_node_path6 = __toESM(require("node:path"));
34002
34129
  var sessionStore2 = /* @__PURE__ */ new Map();
34003
34130
  var activeByProvider2 = /* @__PURE__ */ new Map();
34004
34131
  var MAX_LINES2 = Math.max(50, parseInt(process.env.QUOROOM_PROVIDER_INSTALL_MAX_LINES || "300", 10) || 300);
@@ -34020,13 +34147,16 @@ function getProviderInstallCommand(provider, platform = process.platform) {
34020
34147
  }
34021
34148
  function addGlobalNpmBinToPath(platform = process.platform) {
34022
34149
  const npmCommand = getNpmCommand(platform);
34150
+ const shell = platform === "win32";
34023
34151
  try {
34024
- const npmBin = (0, import_node_child_process5.execFileSync)(npmCommand, ["bin", "-g"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
34152
+ const npmPrefix = (0, import_node_child_process5.execFileSync)(npmCommand, ["prefix", "-g"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"], shell }).toString().trim();
34153
+ if (!npmPrefix) return;
34154
+ const npmBin = platform === "win32" ? npmPrefix : import_node_path6.default.join(npmPrefix, "bin");
34025
34155
  if (!npmBin) return;
34026
34156
  const currentPath = process.env.PATH || "";
34027
- const parts = currentPath.split(import_node_path5.default.delimiter).filter(Boolean);
34157
+ const parts = currentPath.split(import_node_path6.default.delimiter).filter(Boolean);
34028
34158
  if (parts.includes(npmBin)) return;
34029
- process.env.PATH = `${npmBin}${import_node_path5.default.delimiter}${currentPath}`;
34159
+ process.env.PATH = `${npmBin}${import_node_path6.default.delimiter}${currentPath}`;
34030
34160
  } catch {
34031
34161
  }
34032
34162
  }
@@ -34184,7 +34314,9 @@ function startProviderInstallSession(provider) {
34184
34314
  const displayCommand = [cmd.command, ...cmd.args].join(" ");
34185
34315
  const child = (0, import_node_child_process5.spawn)(cmd.command, cmd.args, {
34186
34316
  stdio: ["pipe", "pipe", "pipe"],
34187
- env: { ...process.env, CI: "1", FORCE_COLOR: "0" }
34317
+ env: { ...process.env, CI: "1", FORCE_COLOR: "0" },
34318
+ // Windows needs shell:true to execute .cmd batch wrappers (npm.cmd)
34319
+ shell: process.platform === "win32"
34188
34320
  });
34189
34321
  const startedAt2 = nowIso2();
34190
34322
  const session = {
@@ -34640,11 +34772,21 @@ async function handleWebhookRequest(pathname, body, db2) {
34640
34772
  // src/server/shell-path.ts
34641
34773
  var import_node_child_process6 = require("node:child_process");
34642
34774
  var import_node_fs5 = require("node:fs");
34643
- var import_node_path6 = __toESM(require("node:path"));
34775
+ var import_node_path7 = __toESM(require("node:path"));
34644
34776
  function inheritShellPath() {
34645
- if (process.platform !== "darwin") return;
34777
+ if (process.platform === "win32") {
34778
+ inheritWindowsPath();
34779
+ return;
34780
+ }
34781
+ if (process.platform === "darwin") {
34782
+ inheritDarwinPath();
34783
+ return;
34784
+ }
34785
+ inheritWindowsPath();
34786
+ }
34787
+ function inheritDarwinPath() {
34646
34788
  const currentPath = process.env.PATH || "";
34647
- const currentParts = new Set(currentPath.split(import_node_path6.default.delimiter).filter(Boolean));
34789
+ const currentParts = new Set(currentPath.split(import_node_path7.default.delimiter).filter(Boolean));
34648
34790
  const shells = [process.env.SHELL, "/bin/zsh", "/bin/bash"].filter(Boolean);
34649
34791
  for (const sh of shells) {
34650
34792
  if (!(0, import_node_fs5.existsSync)(sh)) continue;
@@ -34658,7 +34800,7 @@ function inheritShellPath() {
34658
34800
  stdio: ["ignore", "pipe", "ignore"]
34659
34801
  }).trim();
34660
34802
  if (!shellPath) continue;
34661
- const newParts = shellPath.split(import_node_path6.default.delimiter).filter(Boolean);
34803
+ const newParts = shellPath.split(import_node_path7.default.delimiter).filter(Boolean);
34662
34804
  const additions = [];
34663
34805
  for (const p of newParts) {
34664
34806
  if (!currentParts.has(p)) {
@@ -34667,13 +34809,31 @@ function inheritShellPath() {
34667
34809
  }
34668
34810
  }
34669
34811
  if (additions.length > 0) {
34670
- process.env.PATH = `${currentPath}${import_node_path6.default.delimiter}${additions.join(import_node_path6.default.delimiter)}`;
34812
+ process.env.PATH = `${currentPath}${import_node_path7.default.delimiter}${additions.join(import_node_path7.default.delimiter)}`;
34671
34813
  }
34672
34814
  return;
34673
34815
  } catch {
34674
34816
  }
34675
34817
  }
34676
34818
  }
34819
+ function inheritWindowsPath() {
34820
+ const isWindows = process.platform === "win32";
34821
+ const npmCommand = isWindows ? "npm.cmd" : "npm";
34822
+ try {
34823
+ const npmPrefix = (0, import_node_child_process6.execFileSync)(npmCommand, ["prefix", "-g"], {
34824
+ timeout: 5e3,
34825
+ stdio: ["ignore", "pipe", "ignore"],
34826
+ shell: isWindows
34827
+ }).toString().trim();
34828
+ if (!npmPrefix) return;
34829
+ const npmBin = isWindows ? npmPrefix : import_node_path7.default.join(npmPrefix, "bin");
34830
+ const currentPath = process.env.PATH || "";
34831
+ const parts = currentPath.split(import_node_path7.default.delimiter).filter(Boolean);
34832
+ if (parts.includes(npmBin)) return;
34833
+ process.env.PATH = `${npmBin}${import_node_path7.default.delimiter}${currentPath}`;
34834
+ } catch {
34835
+ }
34836
+ }
34677
34837
 
34678
34838
  // src/server/index.ts
34679
34839
  try {
@@ -34773,6 +34933,48 @@ function shellQuote(arg) {
34773
34933
  function windowsQuote(arg) {
34774
34934
  return `"${arg.replace(/"/g, '\\"')}"`;
34775
34935
  }
34936
+ function killProcessListeningOnPort(port) {
34937
+ if (process.platform === "win32") {
34938
+ try {
34939
+ (0, import_node_child_process7.execSync)(
34940
+ `powershell -NoProfile -Command "Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }"`,
34941
+ { stdio: "ignore" }
34942
+ );
34943
+ return true;
34944
+ } catch {
34945
+ }
34946
+ try {
34947
+ const output = (0, import_node_child_process7.execSync)("netstat -ano -p tcp", { encoding: "utf8" });
34948
+ const pids = /* @__PURE__ */ new Set();
34949
+ for (const rawLine of output.split(/\r?\n/)) {
34950
+ const line = rawLine.trim();
34951
+ if (!line) continue;
34952
+ const match = line.match(/^TCP\s+\S+:(\d+)\s+\S+\s+LISTENING\s+(\d+)$/i);
34953
+ if (!match) continue;
34954
+ const linePort = Number.parseInt(match[1] ?? "", 10);
34955
+ const pid = Number.parseInt(match[2] ?? "", 10);
34956
+ if (linePort !== port || !Number.isFinite(pid) || pid <= 0) continue;
34957
+ pids.add(pid);
34958
+ }
34959
+ if (pids.size === 0) return false;
34960
+ for (const pid of pids) {
34961
+ try {
34962
+ (0, import_node_child_process7.execSync)(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
34963
+ } catch {
34964
+ }
34965
+ }
34966
+ return true;
34967
+ } catch {
34968
+ return false;
34969
+ }
34970
+ }
34971
+ try {
34972
+ (0, import_node_child_process7.execSync)(`lsof -ti :${port} | xargs kill -9`, { stdio: "ignore" });
34973
+ return true;
34974
+ } catch {
34975
+ return false;
34976
+ }
34977
+ }
34776
34978
  function scheduleSelfRestart() {
34777
34979
  try {
34778
34980
  const args = [...process.execArgv, ...process.argv.slice(1)];
@@ -34841,7 +35043,7 @@ function maybeLogHttpProfile(method, pathname, statusCode, durationMs) {
34841
35043
  }
34842
35044
  function getCacheControl(filePath, ext) {
34843
35045
  const normalized = filePath.replace(/\\/g, "/");
34844
- const base2 = import_node_path7.default.basename(filePath);
35046
+ const base2 = import_node_path8.default.basename(filePath);
34845
35047
  if (base2 === "sw.js") return "no-cache, no-store, must-revalidate";
34846
35048
  if (ext === ".html") return "no-cache, no-store, must-revalidate";
34847
35049
  if (ext === ".webmanifest") return "public, max-age=3600";
@@ -34860,15 +35062,15 @@ function getCacheControl(filePath, ext) {
34860
35062
  return "no-cache, max-age=0";
34861
35063
  }
34862
35064
  function serveStatic(staticDir, pathname, res) {
34863
- const safePath = import_node_path7.default.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
34864
- let filePath = import_node_path7.default.join(staticDir, safePath);
35065
+ const safePath = import_node_path8.default.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
35066
+ let filePath = import_node_path8.default.join(staticDir, safePath);
34865
35067
  try {
34866
35068
  const stat = import_node_fs6.default.statSync(filePath);
34867
35069
  if (stat.isDirectory()) {
34868
- filePath = import_node_path7.default.join(filePath, "index.html");
35070
+ filePath = import_node_path8.default.join(filePath, "index.html");
34869
35071
  }
34870
35072
  } catch {
34871
- if (import_node_path7.default.extname(safePath)) {
35073
+ if (import_node_path8.default.extname(safePath)) {
34872
35074
  res.writeHead(404, {
34873
35075
  "Content-Type": "text/plain; charset=utf-8",
34874
35076
  "Cache-Control": "no-cache, no-store, must-revalidate"
@@ -34876,9 +35078,9 @@ function serveStatic(staticDir, pathname, res) {
34876
35078
  res.end("Not Found");
34877
35079
  return;
34878
35080
  }
34879
- filePath = import_node_path7.default.join(staticDir, "index.html");
35081
+ filePath = import_node_path8.default.join(staticDir, "index.html");
34880
35082
  }
34881
- const ext = import_node_path7.default.extname(filePath).toLowerCase();
35083
+ const ext = import_node_path8.default.extname(filePath).toLowerCase();
34882
35084
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
34883
35085
  const headers = {
34884
35086
  "Content-Type": contentType,
@@ -35177,10 +35379,68 @@ function patchMcpConfig(configPath, entry) {
35177
35379
  return false;
35178
35380
  }
35179
35381
  }
35382
+ function patchCodexConfig(configPath, nodePath, mcpServerPath, dbPath) {
35383
+ try {
35384
+ if (!import_node_fs6.default.existsSync(configPath)) return false;
35385
+ const raw = import_node_fs6.default.readFileSync(configPath, "utf-8");
35386
+ const lines = raw.split("\n");
35387
+ const filtered = [];
35388
+ let inQuoroomSection = false;
35389
+ for (const line of lines) {
35390
+ if (/^\[mcp_servers\.quoroom[\].]/.test(line)) {
35391
+ inQuoroomSection = true;
35392
+ continue;
35393
+ }
35394
+ if (inQuoroomSection && /^\[/.test(line)) {
35395
+ inQuoroomSection = false;
35396
+ }
35397
+ if (!inQuoroomSection) {
35398
+ filtered.push(line);
35399
+ }
35400
+ }
35401
+ let content = filtered.join("\n").trimEnd();
35402
+ content += `
35403
+
35404
+ [mcp_servers.quoroom]
35405
+ command = '${nodePath}'
35406
+ args = ['${mcpServerPath}']
35407
+
35408
+ [mcp_servers.quoroom.env]
35409
+ QUOROOM_DB_PATH = '${dbPath}'
35410
+ QUOROOM_SOURCE = "codex"
35411
+ `;
35412
+ import_node_fs6.default.writeFileSync(configPath, content);
35413
+ return true;
35414
+ } catch {
35415
+ return false;
35416
+ }
35417
+ }
35418
+ function patchClaudeCodePermissions(home) {
35419
+ try {
35420
+ const settingsPath = import_node_path8.default.join(home, ".claude", "settings.json");
35421
+ if (!import_node_fs6.default.existsSync(settingsPath)) return false;
35422
+ let settings = {};
35423
+ try {
35424
+ settings = JSON.parse(import_node_fs6.default.readFileSync(settingsPath, "utf-8"));
35425
+ } catch {
35426
+ }
35427
+ const perms = settings.permissions ?? {};
35428
+ const allow = Array.isArray(perms.allow) ? [...perms.allow] : [];
35429
+ const pattern = "mcp__quoroom__*";
35430
+ if (allow.includes(pattern)) return false;
35431
+ allow.push(pattern);
35432
+ perms.allow = allow;
35433
+ settings.permissions = perms;
35434
+ import_node_fs6.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
35435
+ return true;
35436
+ } catch {
35437
+ return false;
35438
+ }
35439
+ }
35180
35440
  function registerMcpGlobally(dbPath) {
35181
35441
  try {
35182
- const home = (0, import_node_os5.homedir)();
35183
- const mcpServerPath = import_node_path7.default.join(__dirname, "server.js");
35442
+ const home = (0, import_node_os6.homedir)();
35443
+ const mcpServerPath = import_node_path8.default.join(__dirname, "server.js");
35184
35444
  const nodePath = process.execPath;
35185
35445
  const entry = (source) => ({
35186
35446
  command: nodePath,
@@ -35189,14 +35449,16 @@ function registerMcpGlobally(dbPath) {
35189
35449
  });
35190
35450
  const isWin = process.platform === "win32";
35191
35451
  const isMac = process.platform === "darwin";
35192
- patchMcpConfig(import_node_path7.default.join(home, ".claude.json"), entry("claude-code"));
35193
- const claudeDesktopPath = isWin ? import_node_path7.default.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json") : isMac ? import_node_path7.default.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : import_node_path7.default.join(home, ".config", "Claude", "claude_desktop_config.json");
35452
+ patchMcpConfig(import_node_path8.default.join(home, ".claude.json"), entry("claude-code"));
35453
+ patchClaudeCodePermissions(home);
35454
+ const claudeDesktopPath = isWin ? import_node_path8.default.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json") : isMac ? import_node_path8.default.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : import_node_path8.default.join(home, ".config", "Claude", "claude_desktop_config.json");
35194
35455
  patchMcpConfig(claudeDesktopPath, entry("claude-desktop"));
35195
- patchMcpConfig(import_node_path7.default.join(home, ".cursor", "mcp.json"), entry("cursor"));
35456
+ patchMcpConfig(import_node_path8.default.join(home, ".cursor", "mcp.json"), entry("cursor"));
35196
35457
  patchMcpConfig(
35197
- import_node_path7.default.join(home, ".codeium", "windsurf", "mcp_config.json"),
35458
+ import_node_path8.default.join(home, ".codeium", "windsurf", "mcp_config.json"),
35198
35459
  entry("windsurf")
35199
35460
  );
35461
+ patchCodexConfig(import_node_path8.default.join(home, ".codex", "config.toml"), nodePath, mcpServerPath, dbPath);
35200
35462
  } catch {
35201
35463
  }
35202
35464
  }
@@ -35206,18 +35468,23 @@ function startServer(options = {}) {
35206
35468
  const deploymentMode = getDeploymentMode();
35207
35469
  const bindHost = process.env.QUOROOM_BIND_HOST || (deploymentMode === "cloud" ? DEFAULT_BIND_HOST_CLOUD : DEFAULT_BIND_HOST_LOCAL);
35208
35470
  if (!options.staticDir) {
35209
- const userUiDir = import_node_path7.default.join(USER_APP_DIR, "ui");
35210
- const bundledUiDir = import_node_path7.default.join(__dirname, "../ui");
35211
- if (import_node_fs6.default.existsSync(import_node_path7.default.join(userUiDir, "index.html"))) {
35471
+ const userUiDir = import_node_path8.default.join(USER_APP_DIR, "ui");
35472
+ const bundledUiDir = import_node_path8.default.join(__dirname, "../ui");
35473
+ if (import_node_fs6.default.existsSync(import_node_path8.default.join(userUiDir, "index.html"))) {
35212
35474
  options.staticDir = userUiDir;
35213
35475
  } else if (import_node_fs6.default.existsSync(bundledUiDir)) {
35214
35476
  options.staticDir = bundledUiDir;
35215
35477
  }
35216
35478
  }
35217
- const dbPath = process.env.QUOROOM_DB_PATH || import_node_path7.default.join(options.dataDir ?? getDataDir(), "data.db");
35479
+ const dbPath = process.env.QUOROOM_DB_PATH || import_node_path8.default.join(options.dataDir ?? getDataDir(), "data.db");
35218
35480
  const { server, token, db: serverDb } = createApiServer(options);
35219
35481
  if (!process.env.QUOROOM_SKIP_MCP_REGISTER) {
35220
35482
  registerMcpGlobally(dbPath);
35483
+ eventBus.on("providers", (evt) => {
35484
+ if ((evt.type === "providers:install_status" || evt.type === "providers:auth_status") && evt.data?.status === "completed") {
35485
+ registerMcpGlobally(dbPath);
35486
+ }
35487
+ });
35221
35488
  }
35222
35489
  initCloudSync(serverDb);
35223
35490
  initUpdateChecker();
@@ -35225,6 +35492,7 @@ function startServer(options = {}) {
35225
35492
  startServerRuntime(serverDb);
35226
35493
  function listen() {
35227
35494
  server.listen(port, bindHost, () => {
35495
+ addrInUseAttempts = 0;
35228
35496
  const bound = server.address();
35229
35497
  const boundPort = typeof bound === "object" && bound ? bound.port : port;
35230
35498
  const dashboardUrl = `http://localhost:${boundPort}`;
@@ -35233,18 +35501,25 @@ function startServer(options = {}) {
35233
35501
  console.error(`Deployment mode: ${deploymentMode}`);
35234
35502
  console.error(`Bind host: ${bindHost}`);
35235
35503
  console.error(`Auth token: ${token.slice(0, 8)}...`);
35236
- if (process.env.NODE_ENV === "production" && deploymentMode !== "cloud") {
35504
+ if (process.env.NODE_ENV === "production" && deploymentMode !== "cloud" && !process.env.QUOROOM_NO_AUTO_OPEN) {
35237
35505
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
35238
35506
  (0, import_node_child_process7.exec)(`${cmd} ${dashboardUrl}`);
35239
35507
  }
35240
35508
  });
35241
35509
  }
35510
+ let addrInUseAttempts = 0;
35511
+ const MAX_ADDR_IN_USE_ATTEMPTS = 3;
35242
35512
  server.on("error", (err) => {
35243
35513
  if (err.code === "EADDRINUSE") {
35514
+ addrInUseAttempts += 1;
35244
35515
  console.error(`Port ${port} is in use \u2014 killing existing process...`);
35245
- try {
35246
- (0, import_node_child_process7.execSync)(`lsof -ti :${port} | xargs kill -9`, { stdio: "ignore" });
35247
- } catch {
35516
+ const reclaimed = killProcessListeningOnPort(port);
35517
+ if (!reclaimed) {
35518
+ console.error(`Could not reclaim port ${port} (attempt ${addrInUseAttempts}/${MAX_ADDR_IN_USE_ATTEMPTS}).`);
35519
+ }
35520
+ if (!reclaimed && addrInUseAttempts >= MAX_ADDR_IN_USE_ATTEMPTS) {
35521
+ console.error(`Failed to start: port ${port} is still occupied.`);
35522
+ return;
35248
35523
  }
35249
35524
  setTimeout(listen, 500);
35250
35525
  } else {
@@ -35284,6 +35559,9 @@ function startServer(options = {}) {
35284
35559
  }
35285
35560
  // Annotate the CommonJS export names for ESM import in node:
35286
35561
  0 && (module.exports = {
35562
+ _isLoopbackAddress,
35563
+ _shellQuote,
35564
+ _windowsQuote,
35287
35565
  createApiServer,
35288
35566
  startServer
35289
35567
  });