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.
- package/out/mcp/api-server.js +530 -252
- package/out/mcp/cli.js +705 -356
- package/out/mcp/server.js +79 -31
- package/package.json +9 -2
package/out/mcp/api-server.js
CHANGED
|
@@ -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.
|
|
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": "
|
|
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
|
|
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:
|
|
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
|
|
10929
|
-
var
|
|
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(
|
|
10936
|
-
this.add("GET",
|
|
10945
|
+
get(path6, handler) {
|
|
10946
|
+
this.add("GET", path6, handler);
|
|
10937
10947
|
}
|
|
10938
|
-
post(
|
|
10939
|
-
this.add("POST",
|
|
10948
|
+
post(path6, handler) {
|
|
10949
|
+
this.add("POST", path6, handler);
|
|
10940
10950
|
}
|
|
10941
|
-
patch(
|
|
10942
|
-
this.add("PATCH",
|
|
10951
|
+
patch(path6, handler) {
|
|
10952
|
+
this.add("PATCH", path6, handler);
|
|
10943
10953
|
}
|
|
10944
|
-
put(
|
|
10945
|
-
this.add("PUT",
|
|
10954
|
+
put(path6, handler) {
|
|
10955
|
+
this.add("PUT", path6, handler);
|
|
10946
10956
|
}
|
|
10947
|
-
delete(
|
|
10948
|
-
this.add("DELETE",
|
|
10957
|
+
delete(path6, handler) {
|
|
10958
|
+
this.add("DELETE", path6, handler);
|
|
10949
10959
|
}
|
|
10950
|
-
add(method,
|
|
10960
|
+
add(method, path6, handler) {
|
|
10951
10961
|
const paramNames = [];
|
|
10952
|
-
const patternStr =
|
|
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
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
-
|
|
11444
|
-
-
|
|
11445
|
-
-
|
|
11446
|
-
-
|
|
11447
|
-
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
-
|
|
11451
|
-
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
-
|
|
11455
|
-
-
|
|
11456
|
-
- Use
|
|
11457
|
-
-
|
|
11458
|
-
-
|
|
11459
|
-
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
**
|
|
11469
|
-
**
|
|
11470
|
-
Score so far
|
|
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
|
-
-
|
|
11492
|
-
-
|
|
11493
|
-
-
|
|
11494
|
-
-
|
|
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,
|
|
11779
|
-
const result = db2.prepare("INSERT INTO watches (path, description, action_prompt, room_id) VALUES (?, ?, ?, ?)").run(
|
|
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
|
-
|
|
23173
|
-
|
|
23174
|
-
|
|
23175
|
-
|
|
23176
|
-
|
|
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
|
-
|
|
23357
|
-
|
|
23358
|
-
|
|
23359
|
-
|
|
23360
|
-
|
|
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, (
|
|
23394
|
-
if (
|
|
23395
|
-
if (
|
|
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(), (
|
|
23415
|
-
if (
|
|
23416
|
-
if (
|
|
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
|
|
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(
|
|
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)("
|
|
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 =
|
|
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
|
|
26956
|
-
var
|
|
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
|
|
26961
|
-
var
|
|
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,
|
|
27531
|
-
(0,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
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(
|
|
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
|
|
28122
|
-
const
|
|
28123
|
-
|
|
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
|
-
|
|
28129
|
-
|
|
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
|
|
28137
|
-
const
|
|
28138
|
-
lines.push(`[${
|
|
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
|
|
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 (
|
|
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}
|
|
28263
|
-
|
|
28264
|
-
${modeInstruction}
|
|
28369
|
+
${rawLogs}${contextNote}
|
|
28265
28370
|
|
|
28266
|
-
Write your live commentary
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
31724
|
-
var
|
|
31828
|
+
var import_path5 = require("path");
|
|
31829
|
+
var import_fs5 = require("fs");
|
|
31725
31830
|
var SENSITIVE_HOME_SUFFIXES = [
|
|
31726
|
-
`${
|
|
31727
|
-
`${
|
|
31728
|
-
`${
|
|
31729
|
-
`${
|
|
31730
|
-
`${
|
|
31731
|
-
`${
|
|
31732
|
-
`${
|
|
31733
|
-
`${
|
|
31734
|
-
`${
|
|
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,
|
|
31743
|
-
if (!(0,
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
32044
|
-
var
|
|
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,
|
|
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,
|
|
32666
|
-
const rawPath = process.env.QUOROOM_DB_PATH || (0,
|
|
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
|
|
32693
|
-
var
|
|
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 =
|
|
32699
|
-
var STAGING_DIR =
|
|
32700
|
-
var BOOT_MARKER =
|
|
32701
|
-
var CRASH_COUNT_FILE =
|
|
32702
|
-
var VERSION_FILE =
|
|
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 =
|
|
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:
|
|
32789
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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.
|
|
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
|
|
32868
|
-
if (semverGt(info.minEngineVersion,
|
|
32869
|
-
console.error(`[auto-update] Update requires engine >= ${info.minEngineVersion}, current is ${
|
|
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.
|
|
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] =
|
|
33066
|
-
const total =
|
|
33067
|
-
const free =
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
34157
|
+
const parts = currentPath.split(import_node_path6.default.delimiter).filter(Boolean);
|
|
34028
34158
|
if (parts.includes(npmBin)) return;
|
|
34029
|
-
process.env.PATH = `${npmBin}${
|
|
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
|
|
34775
|
+
var import_node_path7 = __toESM(require("node:path"));
|
|
34644
34776
|
function inheritShellPath() {
|
|
34645
|
-
if (process.platform
|
|
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(
|
|
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(
|
|
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}${
|
|
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 =
|
|
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 =
|
|
34864
|
-
let filePath =
|
|
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 =
|
|
35070
|
+
filePath = import_node_path8.default.join(filePath, "index.html");
|
|
34869
35071
|
}
|
|
34870
35072
|
} catch {
|
|
34871
|
-
if (
|
|
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 =
|
|
35081
|
+
filePath = import_node_path8.default.join(staticDir, "index.html");
|
|
34880
35082
|
}
|
|
34881
|
-
const ext =
|
|
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,
|
|
35183
|
-
const mcpServerPath =
|
|
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(
|
|
35193
|
-
|
|
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(
|
|
35456
|
+
patchMcpConfig(import_node_path8.default.join(home, ".cursor", "mcp.json"), entry("cursor"));
|
|
35196
35457
|
patchMcpConfig(
|
|
35197
|
-
|
|
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 =
|
|
35210
|
-
const bundledUiDir =
|
|
35211
|
-
if (import_node_fs6.default.existsSync(
|
|
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 ||
|
|
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
|
-
|
|
35246
|
-
|
|
35247
|
-
|
|
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
|
});
|