@mindstudio-ai/local-model-tunnel 0.5.26 → 0.5.28
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/dist/{chunk-UP6M4UQ2.js → chunk-JCY2MXIQ.js} +2 -2
- package/dist/{chunk-KVUXXJIF.js → chunk-MCYHE5LW.js} +19 -12
- package/dist/chunk-MCYHE5LW.js.map +1 -0
- package/dist/{chunk-KAGWWCYB.js → chunk-WECAPRKN.js} +433 -159
- package/dist/chunk-WECAPRKN.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/headless.d.ts +5 -2
- package/dist/headless.js +2 -2
- package/dist/index.js +3 -3
- package/dist/{tui-5MGVJRQG.js → tui-3K3QFK5W.js} +7 -6
- package/dist/{tui-5MGVJRQG.js.map → tui-3K3QFK5W.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-KAGWWCYB.js.map +0 -1
- package/dist/chunk-KVUXXJIF.js.map +0 -1
- /package/dist/{chunk-UP6M4UQ2.js.map → chunk-JCY2MXIQ.js.map} +0 -0
|
@@ -88,6 +88,7 @@ function deleteLocalInterfacePath(key) {
|
|
|
88
88
|
|
|
89
89
|
// src/dev/logging/logger.ts
|
|
90
90
|
import fs from "fs";
|
|
91
|
+
import { join } from "path";
|
|
91
92
|
var LEVELS = {
|
|
92
93
|
error: 0,
|
|
93
94
|
warn: 1,
|
|
@@ -97,31 +98,28 @@ var LEVELS = {
|
|
|
97
98
|
var currentLevel = LEVELS.error;
|
|
98
99
|
var writeFn = () => {
|
|
99
100
|
};
|
|
100
|
-
function
|
|
101
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
102
|
-
}
|
|
103
|
-
function write(level, msg, data) {
|
|
101
|
+
function write(level, module, msg, data) {
|
|
104
102
|
if (LEVELS[level] > currentLevel) {
|
|
105
103
|
return;
|
|
106
104
|
}
|
|
107
|
-
const
|
|
105
|
+
const entry = { ts: Date.now(), level, module, msg };
|
|
108
106
|
if (data) {
|
|
109
|
-
|
|
107
|
+
Object.assign(entry, data);
|
|
110
108
|
}
|
|
111
|
-
writeFn(
|
|
109
|
+
writeFn(JSON.stringify(entry));
|
|
112
110
|
}
|
|
113
111
|
var log = {
|
|
114
|
-
error(msg, data) {
|
|
115
|
-
write("error", msg, data);
|
|
112
|
+
error(module, msg, data) {
|
|
113
|
+
write("error", module, msg, data);
|
|
116
114
|
},
|
|
117
|
-
warn(msg, data) {
|
|
118
|
-
write("warn", msg, data);
|
|
115
|
+
warn(module, msg, data) {
|
|
116
|
+
write("warn", module, msg, data);
|
|
119
117
|
},
|
|
120
|
-
info(msg, data) {
|
|
121
|
-
write("info", msg, data);
|
|
118
|
+
info(module, msg, data) {
|
|
119
|
+
write("info", module, msg, data);
|
|
122
120
|
},
|
|
123
|
-
debug(msg, data) {
|
|
124
|
-
write("debug", msg, data);
|
|
121
|
+
debug(module, msg, data) {
|
|
122
|
+
write("debug", module, msg, data);
|
|
125
123
|
}
|
|
126
124
|
};
|
|
127
125
|
function initLoggerHeadless(level = "info") {
|
|
@@ -136,7 +134,9 @@ function initLoggerInteractive(level = "error") {
|
|
|
136
134
|
writeFn = (line) => {
|
|
137
135
|
try {
|
|
138
136
|
if (fd === null) {
|
|
139
|
-
|
|
137
|
+
const logsDir = join(process.cwd(), ".logs");
|
|
138
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
139
|
+
fd = fs.openSync(join(logsDir, "tunnel.ndjson"), "a");
|
|
140
140
|
}
|
|
141
141
|
fs.writeSync(fd, line + "\n");
|
|
142
142
|
} catch {
|
|
@@ -162,7 +162,8 @@ function basePath(appId) {
|
|
|
162
162
|
}
|
|
163
163
|
async function apiRequest(method, url, headers, body) {
|
|
164
164
|
const start = Date.now();
|
|
165
|
-
const
|
|
165
|
+
const httpMethod = method;
|
|
166
|
+
const path2 = url.replace(getApiBaseUrl(), "").replace(/^\/_internal\/v2\/apps\/[^/]+\/dev/, "");
|
|
166
167
|
const response = await fetch(url, {
|
|
167
168
|
method,
|
|
168
169
|
headers,
|
|
@@ -170,16 +171,16 @@ async function apiRequest(method, url, headers, body) {
|
|
|
170
171
|
});
|
|
171
172
|
const duration = Date.now() - start;
|
|
172
173
|
if (response.status === 204) {
|
|
173
|
-
log.debug(
|
|
174
|
+
log.debug("api", "Request complete", { method: httpMethod, path: path2, status: 204, duration });
|
|
174
175
|
return null;
|
|
175
176
|
}
|
|
176
177
|
if (!response.ok) {
|
|
177
178
|
const error = await response.text();
|
|
178
|
-
log.error(
|
|
179
|
-
throw new ApiError(`${
|
|
179
|
+
log.error("api", "Request failed", { method: httpMethod, path: path2, status: response.status, duration, error });
|
|
180
|
+
throw new ApiError(`${httpMethod} ${path2} failed: ${response.status} ${error}`, response.status);
|
|
180
181
|
}
|
|
181
182
|
const data = await response.json();
|
|
182
|
-
log.info(
|
|
183
|
+
log.info("api", "Request complete", { method: httpMethod, path: path2, status: response.status, duration });
|
|
183
184
|
return data;
|
|
184
185
|
}
|
|
185
186
|
async function startDevSession(appId, opts) {
|
|
@@ -276,7 +277,7 @@ var DevPollError = class extends Error {
|
|
|
276
277
|
|
|
277
278
|
// src/dev/logging/ndjson-log.ts
|
|
278
279
|
import fs2 from "fs";
|
|
279
|
-
import { join } from "path";
|
|
280
|
+
import { join as join2 } from "path";
|
|
280
281
|
var NdjsonLog = class {
|
|
281
282
|
constructor(filename, maxLines = 500, keepLines = 300, maxBytes = 2 * 1024 * 1024) {
|
|
282
283
|
this.filename = filename;
|
|
@@ -291,9 +292,9 @@ var NdjsonLog = class {
|
|
|
291
292
|
init(projectRoot) {
|
|
292
293
|
this.close();
|
|
293
294
|
try {
|
|
294
|
-
const logsDir =
|
|
295
|
+
const logsDir = join2(projectRoot, ".logs");
|
|
295
296
|
fs2.mkdirSync(logsDir, { recursive: true });
|
|
296
|
-
this.logPath =
|
|
297
|
+
this.logPath = join2(logsDir, this.filename);
|
|
297
298
|
if (fs2.existsSync(this.logPath)) {
|
|
298
299
|
const content = fs2.readFileSync(this.logPath, "utf-8");
|
|
299
300
|
this.lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
@@ -301,12 +302,12 @@ var NdjsonLog = class {
|
|
|
301
302
|
this.lineCount = 0;
|
|
302
303
|
}
|
|
303
304
|
this.fd = fs2.openSync(this.logPath, "a");
|
|
304
|
-
log.
|
|
305
|
+
log.debug("logging", `${this.filename} log initialized`, {
|
|
305
306
|
path: this.logPath,
|
|
306
307
|
existingEntries: this.lineCount
|
|
307
308
|
});
|
|
308
309
|
} catch (err) {
|
|
309
|
-
log.warn(`Failed to initialize ${this.filename} log`, {
|
|
310
|
+
log.warn("logging", `Failed to initialize ${this.filename} log`, {
|
|
310
311
|
error: err instanceof Error ? err.message : String(err)
|
|
311
312
|
});
|
|
312
313
|
this.fd = null;
|
|
@@ -320,10 +321,7 @@ var NdjsonLog = class {
|
|
|
320
321
|
fs2.writeSync(this.fd, line);
|
|
321
322
|
this.lineCount++;
|
|
322
323
|
this.maybeRotate();
|
|
323
|
-
} catch
|
|
324
|
-
log.debug(`Failed to write ${this.filename} log entry`, {
|
|
325
|
-
error: err instanceof Error ? err.message : String(err)
|
|
326
|
-
});
|
|
324
|
+
} catch {
|
|
327
325
|
}
|
|
328
326
|
}
|
|
329
327
|
close() {
|
|
@@ -355,11 +353,7 @@ var NdjsonLog = class {
|
|
|
355
353
|
fs2.writeFileSync(this.logPath, kept.join("\n") + "\n", "utf-8");
|
|
356
354
|
this.fd = fs2.openSync(this.logPath, "a");
|
|
357
355
|
this.lineCount = kept.length;
|
|
358
|
-
|
|
359
|
-
} catch (err) {
|
|
360
|
-
log.debug(`${this.filename} log rotation failed`, {
|
|
361
|
-
error: err instanceof Error ? err.message : String(err)
|
|
362
|
-
});
|
|
356
|
+
} catch {
|
|
363
357
|
} finally {
|
|
364
358
|
this.rotating = false;
|
|
365
359
|
}
|
|
@@ -373,8 +367,11 @@ function initRequestLog(projectRoot) {
|
|
|
373
367
|
}
|
|
374
368
|
function logMethodExecution(entry) {
|
|
375
369
|
ndjsonLog.append({
|
|
370
|
+
ts: Date.now(),
|
|
371
|
+
level: "info",
|
|
372
|
+
module: "execution",
|
|
373
|
+
msg: entry.result.success ? "Method complete" : "Method failed",
|
|
376
374
|
type: "method",
|
|
377
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
378
375
|
requestId: entry.requestId,
|
|
379
376
|
sessionId: entry.sessionId,
|
|
380
377
|
method: entry.methodExport,
|
|
@@ -393,8 +390,11 @@ function logMethodExecution(entry) {
|
|
|
393
390
|
}
|
|
394
391
|
function logScenarioExecution(entry) {
|
|
395
392
|
ndjsonLog.append({
|
|
393
|
+
ts: Date.now(),
|
|
394
|
+
level: "info",
|
|
395
|
+
module: "execution",
|
|
396
|
+
msg: entry.result?.success ?? false ? "Scenario complete" : "Scenario failed",
|
|
396
397
|
type: "scenario",
|
|
397
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
398
398
|
sessionId: entry.sessionId,
|
|
399
399
|
scenario: {
|
|
400
400
|
id: entry.scenario.id,
|
|
@@ -480,7 +480,7 @@ var devRequestEvents = new DevEventEmitter();
|
|
|
480
480
|
// src/dev/execution/transpiler.ts
|
|
481
481
|
import { unlink, mkdir, readdir } from "fs/promises";
|
|
482
482
|
import { existsSync } from "fs";
|
|
483
|
-
import { resolve, dirname, basename, join as
|
|
483
|
+
import { resolve, dirname, basename, join as join3 } from "path";
|
|
484
484
|
import { build } from "esbuild";
|
|
485
485
|
var Transpiler = class {
|
|
486
486
|
projectRoot;
|
|
@@ -506,18 +506,16 @@ var Transpiler = class {
|
|
|
506
506
|
const start = Date.now();
|
|
507
507
|
const absolutePath = resolve(this.projectRoot, methodPath);
|
|
508
508
|
const name = basename(absolutePath).replace(/\.[^.]+$/, "");
|
|
509
|
-
log.debug("Transpiling method", { methodPath });
|
|
510
509
|
const nodeModulesDir = findNearestNodeModules(dirname(absolutePath));
|
|
511
510
|
if (!nodeModulesDir) {
|
|
512
|
-
log.error("Cannot find node_modules for method", { methodPath, searchStart: dirname(absolutePath) });
|
|
511
|
+
log.error("transpiler", "Cannot find node_modules for method", { methodPath, searchStart: dirname(absolutePath) });
|
|
513
512
|
throw new Error(
|
|
514
513
|
`No node_modules found near ${methodPath}. Run npm install first.`
|
|
515
514
|
);
|
|
516
515
|
}
|
|
517
|
-
|
|
518
|
-
const outDir = join2(nodeModulesDir, ".cache", "mindstudio-dev");
|
|
516
|
+
const outDir = join3(nodeModulesDir, ".cache", "mindstudio-dev");
|
|
519
517
|
await mkdir(outDir, { recursive: true });
|
|
520
|
-
const outfile =
|
|
518
|
+
const outfile = join3(outDir, `${name}.__ms_dev__.mjs`);
|
|
521
519
|
await build({
|
|
522
520
|
entryPoints: [absolutePath],
|
|
523
521
|
bundle: true,
|
|
@@ -530,14 +528,13 @@ var Transpiler = class {
|
|
|
530
528
|
logLevel: "silent"
|
|
531
529
|
});
|
|
532
530
|
this.outputFiles.add(outfile);
|
|
533
|
-
log.info(
|
|
531
|
+
log.info("transpiler", "Method transpiled", { duration: Date.now() - start, methodPath, outfile });
|
|
534
532
|
return outfile;
|
|
535
533
|
}
|
|
536
534
|
/**
|
|
537
535
|
* Clean up all transpiled output files.
|
|
538
536
|
*/
|
|
539
537
|
async cleanup() {
|
|
540
|
-
log.debug("Cleaning up transpiled files", { fileCount: this.outputFiles.size });
|
|
541
538
|
for (const file of this.outputFiles) {
|
|
542
539
|
await unlink(file).catch(() => {
|
|
543
540
|
});
|
|
@@ -549,11 +546,10 @@ async function removeOrphanedDevFiles(dir) {
|
|
|
549
546
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
550
547
|
for (const entry of entries) {
|
|
551
548
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
552
|
-
const fullPath =
|
|
549
|
+
const fullPath = join3(dir, entry.name);
|
|
553
550
|
if (entry.isDirectory()) {
|
|
554
551
|
await removeOrphanedDevFiles(fullPath);
|
|
555
552
|
} else if (entry.name.endsWith(".__ms_dev__.mjs")) {
|
|
556
|
-
log.debug("Removing orphaned transpiled file", { path: fullPath });
|
|
557
553
|
await unlink(fullPath).catch(() => {
|
|
558
554
|
});
|
|
559
555
|
}
|
|
@@ -562,7 +558,7 @@ async function removeOrphanedDevFiles(dir) {
|
|
|
562
558
|
function findNearestNodeModules(startDir) {
|
|
563
559
|
let dir = startDir;
|
|
564
560
|
while (true) {
|
|
565
|
-
const candidate =
|
|
561
|
+
const candidate = join3(dir, "node_modules");
|
|
566
562
|
if (existsSync(candidate)) {
|
|
567
563
|
return candidate;
|
|
568
564
|
}
|
|
@@ -576,7 +572,7 @@ function findNearestNodeModules(startDir) {
|
|
|
576
572
|
// src/dev/execution/executor.ts
|
|
577
573
|
import { fork } from "child_process";
|
|
578
574
|
import { writeFile, unlink as unlink2 } from "fs/promises";
|
|
579
|
-
import { join as
|
|
575
|
+
import { join as join4 } from "path";
|
|
580
576
|
import { tmpdir } from "os";
|
|
581
577
|
import { randomBytes } from "crypto";
|
|
582
578
|
var EXECUTION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
@@ -675,6 +671,10 @@ async function ensureWorker(projectRoot) {
|
|
|
675
671
|
if (worker?.connected && workerProjectRoot === projectRoot) {
|
|
676
672
|
return worker;
|
|
677
673
|
}
|
|
674
|
+
if (worker || workerProjectRoot) {
|
|
675
|
+
const reason = workerProjectRoot !== projectRoot ? "project-root-changed" : "disconnected";
|
|
676
|
+
log.info("executor", "Respawning worker process", { reason });
|
|
677
|
+
}
|
|
678
678
|
if (worker) {
|
|
679
679
|
worker.removeAllListeners();
|
|
680
680
|
worker.kill();
|
|
@@ -685,14 +685,14 @@ async function ensureWorker(projectRoot) {
|
|
|
685
685
|
});
|
|
686
686
|
workerScriptPath = null;
|
|
687
687
|
}
|
|
688
|
-
const scriptPath =
|
|
688
|
+
const scriptPath = join4(
|
|
689
689
|
tmpdir(),
|
|
690
690
|
`ms-dev-worker-${randomBytes(4).toString("hex")}.mjs`
|
|
691
691
|
);
|
|
692
692
|
await writeFile(scriptPath, buildWorkerScript(), "utf-8");
|
|
693
693
|
workerScriptPath = scriptPath;
|
|
694
694
|
workerProjectRoot = projectRoot;
|
|
695
|
-
log.debug("Spawning method execution process", { cwd: projectRoot, scriptPath });
|
|
695
|
+
log.debug("executor", "Spawning method execution process", { cwd: projectRoot, scriptPath });
|
|
696
696
|
const child = fork(scriptPath, [], {
|
|
697
697
|
cwd: projectRoot,
|
|
698
698
|
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
@@ -718,7 +718,7 @@ async function ensureWorker(projectRoot) {
|
|
|
718
718
|
req.resolve(msg);
|
|
719
719
|
});
|
|
720
720
|
child.on("exit", (code) => {
|
|
721
|
-
log.warn("Method execution process exited unexpectedly", { code });
|
|
721
|
+
log.warn("executor", "Method execution process exited unexpectedly", { code });
|
|
722
722
|
for (const [id, req] of pending) {
|
|
723
723
|
clearTimeout(req.timer);
|
|
724
724
|
req.resolve({ success: false, error: { message: `Worker process exited with code ${code}` } });
|
|
@@ -728,10 +728,10 @@ async function ensureWorker(projectRoot) {
|
|
|
728
728
|
});
|
|
729
729
|
child.stderr?.on("data", (chunk) => {
|
|
730
730
|
const text = chunk.toString().trim();
|
|
731
|
-
if (text) log.warn("Method process stderr", { text: text.slice(0, 500) });
|
|
731
|
+
if (text) log.warn("executor", "Method process stderr", { text: text.slice(0, 500) });
|
|
732
732
|
});
|
|
733
733
|
worker = child;
|
|
734
|
-
log.info("Method execution process ready", { pid: child.pid });
|
|
734
|
+
log.info("executor", "Method execution process ready", { pid: child.pid });
|
|
735
735
|
return child;
|
|
736
736
|
}
|
|
737
737
|
var queueTail = Promise.resolve();
|
|
@@ -747,14 +747,14 @@ function executeMethod(opts) {
|
|
|
747
747
|
async function executeMethodInWorker(opts) {
|
|
748
748
|
const w = await ensureWorker(opts.projectRoot);
|
|
749
749
|
const id = randomBytes(8).toString("hex");
|
|
750
|
-
log.debug("Sending method to execution process", { id, methodExport: opts.methodExport });
|
|
750
|
+
log.debug("executor", "Sending method to execution process", { id, methodExport: opts.methodExport });
|
|
751
751
|
return new Promise((resolve2) => {
|
|
752
752
|
const timer = setTimeout(() => {
|
|
753
753
|
pending.delete(id);
|
|
754
|
-
log.warn("Method execution timed out", { id, methodExport: opts.methodExport });
|
|
754
|
+
log.warn("executor", "Method execution timed out", { id, methodExport: opts.methodExport });
|
|
755
755
|
resolve2({
|
|
756
756
|
success: false,
|
|
757
|
-
error: { message: "Method execution timed out after
|
|
757
|
+
error: { message: "Method execution timed out after 30m" }
|
|
758
758
|
});
|
|
759
759
|
}, EXECUTION_TIMEOUT_MS);
|
|
760
760
|
pending.set(id, { resolve: resolve2, timer });
|
|
@@ -977,6 +977,63 @@ function formatErrorForDisplay(error) {
|
|
|
977
977
|
return parts.join("\n");
|
|
978
978
|
}
|
|
979
979
|
|
|
980
|
+
// src/dev/execution/agent-config.ts
|
|
981
|
+
import { readFileSync } from "fs";
|
|
982
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
983
|
+
function readAgentConfig(projectRoot, appConfig) {
|
|
984
|
+
const agentInterface = appConfig.interfaces.find(
|
|
985
|
+
(i) => i.type === "agent" && i.enabled !== false
|
|
986
|
+
);
|
|
987
|
+
if (!agentInterface) {
|
|
988
|
+
throw new Error("No agent interface configured in mindstudio.json");
|
|
989
|
+
}
|
|
990
|
+
const configPath = join5(projectRoot, agentInterface.path);
|
|
991
|
+
let raw;
|
|
992
|
+
try {
|
|
993
|
+
raw = readFileSync(configPath, "utf-8");
|
|
994
|
+
} catch {
|
|
995
|
+
throw new Error(
|
|
996
|
+
`Agent config not found at ${agentInterface.path} \u2014 run your build command`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
const parsed = JSON.parse(raw);
|
|
1000
|
+
const config2 = parsed.agent ?? parsed;
|
|
1001
|
+
const agentDir = dirname2(configPath);
|
|
1002
|
+
const systemPromptPath = join5(agentDir, config2.systemPrompt);
|
|
1003
|
+
let systemPrompt;
|
|
1004
|
+
try {
|
|
1005
|
+
systemPrompt = readFileSync(systemPromptPath, "utf-8");
|
|
1006
|
+
} catch {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
`Agent system prompt not found at ${config2.systemPrompt} \u2014 run your build command`
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
const tools = (config2.tools ?? []).map(
|
|
1012
|
+
(tool) => {
|
|
1013
|
+
const descPath = join5(agentDir, tool.description);
|
|
1014
|
+
let description;
|
|
1015
|
+
try {
|
|
1016
|
+
description = readFileSync(descPath, "utf-8");
|
|
1017
|
+
} catch {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
`Agent tool description not found at ${tool.description} for method "${tool.method}" \u2014 run your build command`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
name: tool.method,
|
|
1024
|
+
description
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
);
|
|
1028
|
+
return {
|
|
1029
|
+
model: config2.model,
|
|
1030
|
+
temperature: config2.temperature,
|
|
1031
|
+
maxTokens: config2.maxTokens,
|
|
1032
|
+
systemPrompt,
|
|
1033
|
+
tools
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
980
1037
|
// src/dev/execution/runner.ts
|
|
981
1038
|
var DevRunner = class {
|
|
982
1039
|
constructor(appId, projectRoot, startOpts = {}) {
|
|
@@ -991,6 +1048,8 @@ var DevRunner = class {
|
|
|
991
1048
|
hadConnectionWarning = false;
|
|
992
1049
|
proxyUrl;
|
|
993
1050
|
proxy = null;
|
|
1051
|
+
appConfig = null;
|
|
1052
|
+
roleOverride = null;
|
|
994
1053
|
// proxyUrl is sent on every poll request so the platform dashboard can
|
|
995
1054
|
// show the developer's preview URL. Also included in the start request
|
|
996
1055
|
// so the dashboard sees it immediately without waiting for the first poll.
|
|
@@ -1001,17 +1060,20 @@ var DevRunner = class {
|
|
|
1001
1060
|
setProxy(proxy) {
|
|
1002
1061
|
this.proxy = proxy;
|
|
1003
1062
|
}
|
|
1063
|
+
setAppConfig(appConfig) {
|
|
1064
|
+
this.appConfig = appConfig;
|
|
1065
|
+
}
|
|
1004
1066
|
async start() {
|
|
1005
1067
|
if (this.isRunning) {
|
|
1006
1068
|
throw new Error("DevRunner is already running");
|
|
1007
1069
|
}
|
|
1008
|
-
log.info("Dev session starting", { appId: this.appId, branch: this.startOpts.branch });
|
|
1070
|
+
log.info("runner", "Dev session starting", { appId: this.appId, branch: this.startOpts.branch });
|
|
1009
1071
|
const session = await startDevSession(this.appId, this.startOpts);
|
|
1010
1072
|
this.session = session;
|
|
1011
1073
|
this.transpiler = new Transpiler(this.projectRoot);
|
|
1012
1074
|
this.isRunning = true;
|
|
1013
1075
|
this.backoffMs = 1e3;
|
|
1014
|
-
log.info("Dev session started", { sessionId: session.sessionId, branch: session.branch });
|
|
1076
|
+
log.info("runner", "Dev session started", { sessionId: session.sessionId, branch: session.branch });
|
|
1015
1077
|
return session;
|
|
1016
1078
|
}
|
|
1017
1079
|
// Begin polling for platform method requests. Call this after all
|
|
@@ -1021,16 +1083,17 @@ var DevRunner = class {
|
|
|
1021
1083
|
this.pollLoop();
|
|
1022
1084
|
}
|
|
1023
1085
|
async stop() {
|
|
1024
|
-
log.info("Dev session stopping");
|
|
1086
|
+
log.info("runner", "Dev session stopping");
|
|
1025
1087
|
this.isRunning = false;
|
|
1026
1088
|
if (this.session) {
|
|
1027
1089
|
try {
|
|
1028
1090
|
await stopDevSession(this.appId, this.session.sessionId);
|
|
1029
1091
|
} catch (err) {
|
|
1030
|
-
log.warn("Failed to stop dev session cleanly", { error: err instanceof Error ? err.message : String(err) });
|
|
1092
|
+
log.warn("runner", "Failed to stop dev session cleanly", { error: err instanceof Error ? err.message : String(err) });
|
|
1031
1093
|
}
|
|
1032
1094
|
this.session = null;
|
|
1033
1095
|
}
|
|
1096
|
+
this.roleOverride = null;
|
|
1034
1097
|
await cleanupWorker();
|
|
1035
1098
|
if (this.transpiler) {
|
|
1036
1099
|
await this.transpiler.cleanup();
|
|
@@ -1043,15 +1106,17 @@ var DevRunner = class {
|
|
|
1043
1106
|
// Set role override for subsequent method executions.
|
|
1044
1107
|
async setImpersonation(roles) {
|
|
1045
1108
|
if (!this.session) return;
|
|
1046
|
-
log.info("Setting role override", { roles });
|
|
1109
|
+
log.info("runner", "Setting role override", { roles });
|
|
1047
1110
|
await impersonate(this.appId, this.session.sessionId, roles);
|
|
1111
|
+
this.roleOverride = roles;
|
|
1048
1112
|
await this.refreshClientContext();
|
|
1049
1113
|
}
|
|
1050
1114
|
// Clear role override — revert to session's default roles.
|
|
1051
1115
|
async clearImpersonation() {
|
|
1052
1116
|
if (!this.session) return;
|
|
1053
|
-
log.info("Clearing role override");
|
|
1117
|
+
log.info("runner", "Clearing role override");
|
|
1054
1118
|
await impersonate(this.appId, this.session.sessionId, null);
|
|
1119
|
+
this.roleOverride = null;
|
|
1055
1120
|
await this.refreshClientContext();
|
|
1056
1121
|
}
|
|
1057
1122
|
// Fetch fresh clientContext from platform and update the proxy.
|
|
@@ -1063,7 +1128,7 @@ var DevRunner = class {
|
|
|
1063
1128
|
this.session.clientContext = context;
|
|
1064
1129
|
this.proxy.updateClientContext(context);
|
|
1065
1130
|
} catch (err) {
|
|
1066
|
-
log.warn("Failed to refresh session context after role change", { error: err instanceof Error ? err.message : String(err) });
|
|
1131
|
+
log.warn("runner", "Failed to refresh session context after role change", { error: err instanceof Error ? err.message : String(err) });
|
|
1067
1132
|
}
|
|
1068
1133
|
}
|
|
1069
1134
|
// Run a method directly (not via poll loop). Used by headless stdin commands
|
|
@@ -1074,15 +1139,22 @@ var DevRunner = class {
|
|
|
1074
1139
|
}
|
|
1075
1140
|
const requestId = randomBytes2(8).toString("hex");
|
|
1076
1141
|
const startTime = Date.now();
|
|
1077
|
-
log.info("Method received
|
|
1142
|
+
log.info("runner", "Method received", { requestId, method: opts.methodExport, source: "direct", sessionId: this.session.sessionId });
|
|
1078
1143
|
try {
|
|
1079
1144
|
const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
|
|
1080
1145
|
const transpiledPath = await this.transpiler.transpile(opts.methodPath);
|
|
1146
|
+
const auth = this.roleOverride ? {
|
|
1147
|
+
userId: this.session.auth.userId,
|
|
1148
|
+
roleAssignments: this.roleOverride.map((roleName) => ({
|
|
1149
|
+
userId: this.session.auth.userId,
|
|
1150
|
+
roleName
|
|
1151
|
+
}))
|
|
1152
|
+
} : this.session.auth;
|
|
1081
1153
|
const result = await executeMethod({
|
|
1082
1154
|
transpiledPath,
|
|
1083
1155
|
methodExport: opts.methodExport,
|
|
1084
1156
|
input: opts.input,
|
|
1085
|
-
auth
|
|
1157
|
+
auth,
|
|
1086
1158
|
databases: this.session.databases,
|
|
1087
1159
|
authorizationToken,
|
|
1088
1160
|
apiBaseUrl: getApiBaseUrl(),
|
|
@@ -1090,13 +1162,14 @@ var DevRunner = class {
|
|
|
1090
1162
|
});
|
|
1091
1163
|
const duration = Date.now() - startTime;
|
|
1092
1164
|
if (result.success) {
|
|
1093
|
-
log.info("Method complete", { requestId, method: opts.methodExport, duration });
|
|
1165
|
+
log.info("runner", "Method complete", { requestId, method: opts.methodExport, duration, sessionId: this.session.sessionId });
|
|
1094
1166
|
} else {
|
|
1095
|
-
log.warn("Method failed", {
|
|
1167
|
+
log.warn("runner", "Method failed", {
|
|
1096
1168
|
requestId,
|
|
1097
1169
|
method: opts.methodExport,
|
|
1098
1170
|
duration,
|
|
1099
|
-
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1171
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0,
|
|
1172
|
+
sessionId: this.session.sessionId
|
|
1100
1173
|
});
|
|
1101
1174
|
}
|
|
1102
1175
|
logMethodExecution({
|
|
@@ -1120,7 +1193,7 @@ var DevRunner = class {
|
|
|
1120
1193
|
} catch (err) {
|
|
1121
1194
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1122
1195
|
const duration = Date.now() - startTime;
|
|
1123
|
-
log.error("Method error", { requestId, method: opts.methodExport, duration, error: message });
|
|
1196
|
+
log.error("runner", "Method execution error", { requestId, method: opts.methodExport, duration, error: message, sessionId: this.session.sessionId });
|
|
1124
1197
|
logMethodExecution({
|
|
1125
1198
|
requestId,
|
|
1126
1199
|
sessionId: this.session.sessionId,
|
|
@@ -1143,15 +1216,15 @@ var DevRunner = class {
|
|
|
1143
1216
|
}
|
|
1144
1217
|
const startTime = Date.now();
|
|
1145
1218
|
const scenarioName = scenario.name ?? scenario.export;
|
|
1146
|
-
log.info("Scenario starting", { id: scenario.id, name: scenarioName });
|
|
1219
|
+
log.info("runner", "Scenario starting", { id: scenario.id, name: scenarioName });
|
|
1147
1220
|
try {
|
|
1148
|
-
log.
|
|
1221
|
+
log.debug("runner", "Resetting database for scenario");
|
|
1149
1222
|
const databases = await resetDevDatabase(this.appId, this.session.sessionId, "truncate");
|
|
1150
1223
|
this.session.databases = databases;
|
|
1151
|
-
log.
|
|
1224
|
+
log.debug("runner", "Transpiling scenario", { path: scenario.path });
|
|
1152
1225
|
const transpiledPath = await this.transpiler.transpile(scenario.path);
|
|
1153
1226
|
const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
|
|
1154
|
-
log.
|
|
1227
|
+
log.debug("runner", "Running scenario seed function", { export: scenario.export });
|
|
1155
1228
|
const result = await executeMethod({
|
|
1156
1229
|
transpiledPath,
|
|
1157
1230
|
methodExport: scenario.export,
|
|
@@ -1164,7 +1237,7 @@ var DevRunner = class {
|
|
|
1164
1237
|
});
|
|
1165
1238
|
if (!result.success) {
|
|
1166
1239
|
const error = result.error?.message ?? "Scenario seed failed";
|
|
1167
|
-
log.error("Scenario seed function failed", { id: scenario.id, error });
|
|
1240
|
+
log.error("runner", "Scenario seed function failed", { id: scenario.id, name: scenarioName, duration: Date.now() - startTime, error });
|
|
1168
1241
|
logScenarioExecution({
|
|
1169
1242
|
sessionId: this.session.sessionId,
|
|
1170
1243
|
scenario,
|
|
@@ -1175,12 +1248,12 @@ var DevRunner = class {
|
|
|
1175
1248
|
return { success: false, databases, error };
|
|
1176
1249
|
}
|
|
1177
1250
|
if (scenario.roles.length > 0) {
|
|
1178
|
-
log.
|
|
1251
|
+
log.debug("runner", "Setting role override for scenario", { roles: scenario.roles });
|
|
1179
1252
|
await impersonate(this.appId, this.session.sessionId, scenario.roles);
|
|
1180
1253
|
await this.refreshClientContext();
|
|
1181
1254
|
}
|
|
1182
1255
|
const duration = Date.now() - startTime;
|
|
1183
|
-
log.info("Scenario complete", { id: scenario.id, duration, roles: scenario.roles });
|
|
1256
|
+
log.info("runner", "Scenario complete", { id: scenario.id, name: scenarioName, duration, roles: scenario.roles });
|
|
1184
1257
|
logScenarioExecution({
|
|
1185
1258
|
sessionId: this.session.sessionId,
|
|
1186
1259
|
scenario,
|
|
@@ -1191,7 +1264,7 @@ var DevRunner = class {
|
|
|
1191
1264
|
return { success: true, databases };
|
|
1192
1265
|
} catch (err) {
|
|
1193
1266
|
const error = err instanceof Error ? err.message : "Unknown error";
|
|
1194
|
-
log.error("Scenario failed", { id: scenario.id, error });
|
|
1267
|
+
log.error("runner", "Scenario failed", { id: scenario.id, name: scenarioName, duration: Date.now() - startTime, error });
|
|
1195
1268
|
logScenarioExecution({
|
|
1196
1269
|
sessionId: this.session.sessionId,
|
|
1197
1270
|
scenario,
|
|
@@ -1213,7 +1286,7 @@ var DevRunner = class {
|
|
|
1213
1286
|
);
|
|
1214
1287
|
if (this.hadConnectionWarning) {
|
|
1215
1288
|
this.hadConnectionWarning = false;
|
|
1216
|
-
log.info("Connection to platform restored");
|
|
1289
|
+
log.info("runner", "Connection to platform restored");
|
|
1217
1290
|
devRequestEvents.emitConnectionRestored();
|
|
1218
1291
|
}
|
|
1219
1292
|
if (request) {
|
|
@@ -1222,37 +1295,39 @@ var DevRunner = class {
|
|
|
1222
1295
|
this.backoffMs = 1e3;
|
|
1223
1296
|
} catch (error) {
|
|
1224
1297
|
if (error instanceof DevPollError && error.statusCode === 404) {
|
|
1225
|
-
log.error("Dev session expired", { statusCode: 404 });
|
|
1298
|
+
log.error("runner", "Dev session expired", { statusCode: 404 });
|
|
1226
1299
|
devRequestEvents.emitSessionExpired();
|
|
1227
1300
|
this.isRunning = false;
|
|
1228
1301
|
return;
|
|
1229
1302
|
}
|
|
1230
1303
|
if ((error instanceof DevPollError || error instanceof ApiError) && error.statusCode === 401) {
|
|
1231
|
-
log.warn("Session token expired, re-authenticating");
|
|
1232
1304
|
const refreshed = await this.refreshAuth();
|
|
1233
1305
|
if (refreshed) {
|
|
1234
1306
|
this.backoffMs = 1e3;
|
|
1235
1307
|
continue;
|
|
1236
1308
|
}
|
|
1237
|
-
log.error("Re-authentication failed");
|
|
1309
|
+
log.error("runner", "Re-authentication failed");
|
|
1238
1310
|
devRequestEvents.emitSessionExpired();
|
|
1239
1311
|
this.isRunning = false;
|
|
1240
1312
|
return;
|
|
1241
1313
|
}
|
|
1242
1314
|
if (!this.hadConnectionWarning) {
|
|
1243
1315
|
this.hadConnectionWarning = true;
|
|
1244
|
-
log.warn("Lost connection to platform, retrying");
|
|
1316
|
+
log.warn("runner", "Lost connection to platform, retrying");
|
|
1245
1317
|
devRequestEvents.emitConnectionWarning(
|
|
1246
1318
|
"Lost connection to platform, retrying..."
|
|
1247
1319
|
);
|
|
1248
1320
|
}
|
|
1249
|
-
log.debug("Backing off", { ms: this.backoffMs });
|
|
1250
1321
|
await this.sleep(this.backoffMs);
|
|
1251
1322
|
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
1252
1323
|
}
|
|
1253
1324
|
}
|
|
1254
1325
|
}
|
|
1255
1326
|
async handleRequest(request) {
|
|
1327
|
+
if (request.type === "get-agent-config") {
|
|
1328
|
+
await this.handleGetAgentConfig(request);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1256
1331
|
const startTime = Date.now();
|
|
1257
1332
|
devRequestEvents.emitStart({
|
|
1258
1333
|
id: request.requestId,
|
|
@@ -1260,9 +1335,8 @@ var DevRunner = class {
|
|
|
1260
1335
|
method: request.methodExport,
|
|
1261
1336
|
timestamp: startTime
|
|
1262
1337
|
});
|
|
1263
|
-
log.info("Method received", { requestId: request.requestId, method: request.methodExport });
|
|
1338
|
+
log.info("runner", "Method received", { requestId: request.requestId, method: request.methodExport, source: "poll", sessionId: this.session.sessionId });
|
|
1264
1339
|
try {
|
|
1265
|
-
log.debug("Transpiling method", { path: request.methodPath });
|
|
1266
1340
|
const transpiledPath = await this.transpiler.transpile(request.methodPath);
|
|
1267
1341
|
const auth = request.roleOverride ? {
|
|
1268
1342
|
userId: this.session.auth.userId,
|
|
@@ -1298,13 +1372,14 @@ var DevRunner = class {
|
|
|
1298
1372
|
);
|
|
1299
1373
|
const duration = Date.now() - startTime;
|
|
1300
1374
|
if (result.success) {
|
|
1301
|
-
log.info("Method complete", { requestId: request.requestId, method: request.methodExport, duration });
|
|
1375
|
+
log.info("runner", "Method complete", { requestId: request.requestId, method: request.methodExport, duration, sessionId: this.session.sessionId });
|
|
1302
1376
|
} else {
|
|
1303
|
-
log.warn("Method failed", {
|
|
1377
|
+
log.warn("runner", "Method failed", {
|
|
1304
1378
|
requestId: request.requestId,
|
|
1305
1379
|
method: request.methodExport,
|
|
1306
1380
|
duration,
|
|
1307
|
-
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1381
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0,
|
|
1382
|
+
sessionId: this.session.sessionId
|
|
1308
1383
|
});
|
|
1309
1384
|
}
|
|
1310
1385
|
logMethodExecution({
|
|
@@ -1328,7 +1403,7 @@ var DevRunner = class {
|
|
|
1328
1403
|
} catch (error) {
|
|
1329
1404
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1330
1405
|
const duration = Date.now() - startTime;
|
|
1331
|
-
log.error("Method error", { requestId: request.requestId, method: request.methodExport, duration, error: message });
|
|
1406
|
+
log.error("runner", "Method execution error", { requestId: request.requestId, method: request.methodExport, duration, error: message, sessionId: this.session.sessionId });
|
|
1332
1407
|
try {
|
|
1333
1408
|
await submitDevResult(
|
|
1334
1409
|
this.appId,
|
|
@@ -1341,7 +1416,7 @@ var DevRunner = class {
|
|
|
1341
1416
|
}
|
|
1342
1417
|
);
|
|
1343
1418
|
} catch (submitErr) {
|
|
1344
|
-
log.error("Failed to report method error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
|
|
1419
|
+
log.error("runner", "Failed to report method error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
|
|
1345
1420
|
}
|
|
1346
1421
|
logMethodExecution({
|
|
1347
1422
|
requestId: request.requestId,
|
|
@@ -1363,6 +1438,44 @@ var DevRunner = class {
|
|
|
1363
1438
|
});
|
|
1364
1439
|
}
|
|
1365
1440
|
}
|
|
1441
|
+
async handleGetAgentConfig(request) {
|
|
1442
|
+
const startTime = Date.now();
|
|
1443
|
+
log.info("runner", "Agent config requested", { requestId: request.requestId, sessionId: this.session.sessionId });
|
|
1444
|
+
try {
|
|
1445
|
+
if (!this.appConfig) {
|
|
1446
|
+
throw new Error("App config not available");
|
|
1447
|
+
}
|
|
1448
|
+
const bundle = readAgentConfig(this.projectRoot, this.appConfig);
|
|
1449
|
+
await submitDevResult(
|
|
1450
|
+
this.appId,
|
|
1451
|
+
this.session.sessionId,
|
|
1452
|
+
request.requestId,
|
|
1453
|
+
{
|
|
1454
|
+
type: "get-agent-config",
|
|
1455
|
+
success: true,
|
|
1456
|
+
output: bundle
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
log.info("runner", "Agent config sent", { requestId: request.requestId, duration: Date.now() - startTime });
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1462
|
+
log.error("runner", "Agent config failed", { requestId: request.requestId, error: message });
|
|
1463
|
+
try {
|
|
1464
|
+
await submitDevResult(
|
|
1465
|
+
this.appId,
|
|
1466
|
+
this.session.sessionId,
|
|
1467
|
+
request.requestId,
|
|
1468
|
+
{
|
|
1469
|
+
type: "get-agent-config",
|
|
1470
|
+
success: false,
|
|
1471
|
+
error: { message }
|
|
1472
|
+
}
|
|
1473
|
+
);
|
|
1474
|
+
} catch (submitErr) {
|
|
1475
|
+
log.error("runner", "Failed to report agent config error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1366
1479
|
/**
|
|
1367
1480
|
* Attempt to refresh expired auth credentials via the device auth flow.
|
|
1368
1481
|
* Opens the browser for the user to re-authorize, polls for the new token.
|
|
@@ -1372,14 +1485,14 @@ var DevRunner = class {
|
|
|
1372
1485
|
const POLL_INTERVAL = 2e3;
|
|
1373
1486
|
const MAX_ATTEMPTS = 30;
|
|
1374
1487
|
try {
|
|
1375
|
-
log.info("Session token expired, requesting re-authentication");
|
|
1488
|
+
log.info("runner", "Session token expired, requesting re-authentication");
|
|
1376
1489
|
const { url, token } = await requestDeviceAuth();
|
|
1377
1490
|
devRequestEvents.emitAuthRefreshStart(url);
|
|
1378
1491
|
try {
|
|
1379
1492
|
const open = (await import("open")).default;
|
|
1380
1493
|
await open(url);
|
|
1381
1494
|
} catch {
|
|
1382
|
-
log.warn("Could not open browser \u2014 visit URL to re-authenticate");
|
|
1495
|
+
log.warn("runner", "Could not open browser \u2014 visit URL to re-authenticate");
|
|
1383
1496
|
}
|
|
1384
1497
|
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
1385
1498
|
await this.sleep(POLL_INTERVAL);
|
|
@@ -1390,7 +1503,7 @@ var DevRunner = class {
|
|
|
1390
1503
|
if (result.userId) {
|
|
1391
1504
|
setUserId(result.userId);
|
|
1392
1505
|
}
|
|
1393
|
-
log.info("Re-authentication successful");
|
|
1506
|
+
log.info("runner", "Re-authentication successful");
|
|
1394
1507
|
devRequestEvents.emitAuthRefreshSuccess();
|
|
1395
1508
|
return true;
|
|
1396
1509
|
}
|
|
@@ -1398,11 +1511,11 @@ var DevRunner = class {
|
|
|
1398
1511
|
break;
|
|
1399
1512
|
}
|
|
1400
1513
|
}
|
|
1401
|
-
log.error("Re-authentication timed out or was denied");
|
|
1514
|
+
log.error("runner", "Re-authentication timed out or was denied");
|
|
1402
1515
|
devRequestEvents.emitAuthRefreshFailed();
|
|
1403
1516
|
return false;
|
|
1404
1517
|
} catch (err) {
|
|
1405
|
-
log.error("Re-authentication failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1518
|
+
log.error("runner", "Re-authentication failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1406
1519
|
devRequestEvents.emitAuthRefreshFailed();
|
|
1407
1520
|
return false;
|
|
1408
1521
|
}
|
|
@@ -1417,10 +1530,19 @@ var ndjsonLog2 = new NdjsonLog("browser.ndjson");
|
|
|
1417
1530
|
function initBrowserLog(projectRoot) {
|
|
1418
1531
|
ndjsonLog2.init(projectRoot);
|
|
1419
1532
|
}
|
|
1533
|
+
function inferLevel(entry) {
|
|
1534
|
+
const type = entry.type;
|
|
1535
|
+
if (type === "error") return "error";
|
|
1536
|
+
const level = entry.level;
|
|
1537
|
+
if (level === "warn" || level === "error" || level === "debug") return level;
|
|
1538
|
+
return "info";
|
|
1539
|
+
}
|
|
1420
1540
|
function appendBrowserLogEntries(entries) {
|
|
1421
1541
|
for (const entry of entries) {
|
|
1422
1542
|
ndjsonLog2.append({
|
|
1423
|
-
|
|
1543
|
+
ts: Date.now(),
|
|
1544
|
+
level: inferLevel(entry),
|
|
1545
|
+
module: "browser",
|
|
1424
1546
|
...entry
|
|
1425
1547
|
});
|
|
1426
1548
|
}
|
|
@@ -1450,14 +1572,14 @@ var ClientRegistry = class {
|
|
|
1450
1572
|
alive: true,
|
|
1451
1573
|
activeCommandId: null
|
|
1452
1574
|
});
|
|
1453
|
-
log.info("Browser client connected", { clientId: id, mode: hello.mode, url: hello.url });
|
|
1575
|
+
log.info("proxy", "Browser client connected", { clientId: id, mode: hello.mode, url: hello.url });
|
|
1454
1576
|
return id;
|
|
1455
1577
|
}
|
|
1456
1578
|
remove(id) {
|
|
1457
1579
|
const client = this.clients.get(id);
|
|
1458
1580
|
if (client) {
|
|
1459
1581
|
this.clients.delete(id);
|
|
1460
|
-
log.info("Browser client disconnected", { clientId: id, mode: client.mode });
|
|
1582
|
+
log.info("proxy", "Browser client disconnected", { clientId: id, mode: client.mode });
|
|
1461
1583
|
}
|
|
1462
1584
|
return client;
|
|
1463
1585
|
}
|
|
@@ -1473,11 +1595,16 @@ var ClientRegistry = class {
|
|
|
1473
1595
|
let fallback = null;
|
|
1474
1596
|
for (const client of this.clients.values()) {
|
|
1475
1597
|
if (client.activeCommandId) continue;
|
|
1598
|
+
if (client.mode === "mirror") continue;
|
|
1476
1599
|
if (client.mode === "iframe") return client;
|
|
1477
1600
|
if (!fallback) fallback = client;
|
|
1478
1601
|
}
|
|
1479
1602
|
return fallback;
|
|
1480
1603
|
}
|
|
1604
|
+
/** Get all connected mirror-mode clients (for relaying mirror events). */
|
|
1605
|
+
getMirrorClients() {
|
|
1606
|
+
return [...this.clients.values()].filter((c) => c.mode === "mirror");
|
|
1607
|
+
}
|
|
1481
1608
|
getAll() {
|
|
1482
1609
|
return [...this.clients.values()];
|
|
1483
1610
|
}
|
|
@@ -1513,7 +1640,7 @@ var ClientRegistry = class {
|
|
|
1513
1640
|
const removed = [];
|
|
1514
1641
|
for (const client of this.clients.values()) {
|
|
1515
1642
|
if (!client.alive) {
|
|
1516
|
-
log.warn("Browser client timed out (no pong)", { clientId: client.id, activeCommandId: client.activeCommandId });
|
|
1643
|
+
log.warn("proxy", "Browser client timed out (no pong)", { clientId: client.id, activeCommandId: client.activeCommandId });
|
|
1517
1644
|
removed.push({ clientId: client.id, activeCommandId: client.activeCommandId });
|
|
1518
1645
|
this.clients.delete(client.id);
|
|
1519
1646
|
client.ws.terminate();
|
|
@@ -1548,7 +1675,6 @@ var DevProxy = class _DevProxy {
|
|
|
1548
1675
|
static HELLO_TIMEOUT = 5e3;
|
|
1549
1676
|
updateClientContext(context) {
|
|
1550
1677
|
this.clientContext = context;
|
|
1551
|
-
log.info("Dev proxy context updated after role change");
|
|
1552
1678
|
}
|
|
1553
1679
|
/**
|
|
1554
1680
|
* Whether any browser agent is actively connected via WebSocket.
|
|
@@ -1563,13 +1689,26 @@ var DevProxy = class _DevProxy {
|
|
|
1563
1689
|
dispatchBrowserCommand(steps, timeoutMs = 12e4) {
|
|
1564
1690
|
if (!this.clients.hasConnected()) {
|
|
1565
1691
|
return Promise.reject(
|
|
1566
|
-
new Error(
|
|
1692
|
+
new Error(
|
|
1693
|
+
"No browser connected, please refresh the MindStudio preview"
|
|
1694
|
+
)
|
|
1567
1695
|
);
|
|
1568
1696
|
}
|
|
1569
1697
|
const id = randomBytes4(4).toString("hex");
|
|
1570
1698
|
return new Promise((resolve2, reject) => {
|
|
1571
|
-
this.commandQueue.push({
|
|
1572
|
-
|
|
1699
|
+
this.commandQueue.push({
|
|
1700
|
+
id,
|
|
1701
|
+
steps,
|
|
1702
|
+
timeoutMs,
|
|
1703
|
+
resolve: resolve2,
|
|
1704
|
+
reject,
|
|
1705
|
+
queuedAt: Date.now()
|
|
1706
|
+
});
|
|
1707
|
+
log.debug("proxy", "Browser command queued", {
|
|
1708
|
+
id,
|
|
1709
|
+
queueLength: this.commandQueue.length,
|
|
1710
|
+
commands: steps.map((s) => s.command)
|
|
1711
|
+
});
|
|
1573
1712
|
this.drainCommandQueue();
|
|
1574
1713
|
});
|
|
1575
1714
|
}
|
|
@@ -1582,12 +1721,22 @@ var DevProxy = class _DevProxy {
|
|
|
1582
1721
|
if (!target) break;
|
|
1583
1722
|
const queued = this.commandQueue.shift();
|
|
1584
1723
|
const { id, steps, timeoutMs, resolve: resolve2, reject } = queued;
|
|
1585
|
-
log.info("Browser command sent", {
|
|
1724
|
+
log.info("proxy", "Browser command sent", {
|
|
1725
|
+
id,
|
|
1726
|
+
clientId: target.id,
|
|
1727
|
+
mode: target.mode,
|
|
1728
|
+
stepCount: steps.length,
|
|
1729
|
+
commands: steps.map((s) => s.command),
|
|
1730
|
+
queueWaitMs: Date.now() - queued.queuedAt
|
|
1731
|
+
});
|
|
1586
1732
|
const timeout = setTimeout(() => {
|
|
1587
1733
|
this.pendingResults.delete(id);
|
|
1588
1734
|
const client = this.clients.findByCommandId(id);
|
|
1589
1735
|
if (client) client.activeCommandId = null;
|
|
1590
|
-
log.warn("Browser command timed out", {
|
|
1736
|
+
log.warn("proxy", "Browser command timed out", {
|
|
1737
|
+
id,
|
|
1738
|
+
pendingCount: this.pendingResults.size
|
|
1739
|
+
});
|
|
1591
1740
|
reject(new Error("Browser command timed out"));
|
|
1592
1741
|
this.drainCommandQueue();
|
|
1593
1742
|
}, timeoutMs);
|
|
@@ -1599,6 +1748,10 @@ var DevProxy = class _DevProxy {
|
|
|
1599
1748
|
this.pendingResults.delete(id);
|
|
1600
1749
|
clearTimeout(timeout);
|
|
1601
1750
|
target.activeCommandId = null;
|
|
1751
|
+
log.warn("proxy", "Browser command send failed", {
|
|
1752
|
+
id,
|
|
1753
|
+
clientId: target.id
|
|
1754
|
+
});
|
|
1602
1755
|
reject(new Error("Failed to send command to browser"));
|
|
1603
1756
|
}
|
|
1604
1757
|
}
|
|
@@ -1609,7 +1762,10 @@ var DevProxy = class _DevProxy {
|
|
|
1609
1762
|
broadcastToClients(action, payload) {
|
|
1610
1763
|
const msg = JSON.stringify({ type: "broadcast", action, payload });
|
|
1611
1764
|
const clients = this.clients.getAll();
|
|
1612
|
-
log.info("Broadcasting to browser clients", {
|
|
1765
|
+
log.info("proxy", "Broadcasting to browser clients", {
|
|
1766
|
+
action,
|
|
1767
|
+
clientCount: clients.length
|
|
1768
|
+
});
|
|
1613
1769
|
for (const client of clients) {
|
|
1614
1770
|
try {
|
|
1615
1771
|
client.ws.send(msg);
|
|
@@ -1640,10 +1796,13 @@ var DevProxy = class _DevProxy {
|
|
|
1640
1796
|
this.proxyPort = assignedPort;
|
|
1641
1797
|
this.startHealthCheck();
|
|
1642
1798
|
this.startPingTimer();
|
|
1643
|
-
log.info("Dev proxy started", {
|
|
1799
|
+
log.info("proxy", "Dev proxy started", {
|
|
1800
|
+
port: assignedPort,
|
|
1801
|
+
bind: this.bindAddress
|
|
1802
|
+
});
|
|
1644
1803
|
return assignedPort;
|
|
1645
1804
|
} catch {
|
|
1646
|
-
log.warn("Proxy port in use, trying next", { port });
|
|
1805
|
+
log.warn("proxy", "Proxy port in use, trying next", { port });
|
|
1647
1806
|
}
|
|
1648
1807
|
}
|
|
1649
1808
|
throw new Error("Failed to start proxy server");
|
|
@@ -1681,7 +1840,7 @@ var DevProxy = class _DevProxy {
|
|
|
1681
1840
|
this.wss = null;
|
|
1682
1841
|
}
|
|
1683
1842
|
if (this.server) {
|
|
1684
|
-
log.info("Dev proxy stopping");
|
|
1843
|
+
log.info("proxy", "Dev proxy stopping");
|
|
1685
1844
|
this.server.close();
|
|
1686
1845
|
this.server = null;
|
|
1687
1846
|
this.proxyPort = null;
|
|
@@ -1706,7 +1865,10 @@ var DevProxy = class _DevProxy {
|
|
|
1706
1865
|
let clientId = null;
|
|
1707
1866
|
const helloTimeout = setTimeout(() => {
|
|
1708
1867
|
if (!clientId) {
|
|
1709
|
-
log.warn(
|
|
1868
|
+
log.warn(
|
|
1869
|
+
"proxy",
|
|
1870
|
+
"Browser WS client did not send hello in time, closing"
|
|
1871
|
+
);
|
|
1710
1872
|
ws.close(4e3, "Hello timeout");
|
|
1711
1873
|
}
|
|
1712
1874
|
}, _DevProxy.HELLO_TIMEOUT);
|
|
@@ -1723,8 +1885,11 @@ var DevProxy = class _DevProxy {
|
|
|
1723
1885
|
return;
|
|
1724
1886
|
}
|
|
1725
1887
|
clearTimeout(helloTimeout);
|
|
1726
|
-
const mode = msg.mode === "iframe" ? "iframe" : "standalone";
|
|
1727
|
-
const viewport = msg.viewport || {
|
|
1888
|
+
const mode = msg.mode === "iframe" ? "iframe" : msg.mode === "mirror" ? "mirror" : "standalone";
|
|
1889
|
+
const viewport = msg.viewport || {
|
|
1890
|
+
w: 0,
|
|
1891
|
+
h: 0
|
|
1892
|
+
};
|
|
1728
1893
|
clientId = this.clients.add(ws, {
|
|
1729
1894
|
mode,
|
|
1730
1895
|
url: String(msg.url || ""),
|
|
@@ -1742,6 +1907,9 @@ var DevProxy = class _DevProxy {
|
|
|
1742
1907
|
appendBrowserLogEntries(msg.entries);
|
|
1743
1908
|
}
|
|
1744
1909
|
break;
|
|
1910
|
+
case "mirror":
|
|
1911
|
+
this.relayMirrorEvents(data.toString());
|
|
1912
|
+
break;
|
|
1745
1913
|
}
|
|
1746
1914
|
});
|
|
1747
1915
|
ws.on("pong", () => {
|
|
@@ -1752,7 +1920,10 @@ var DevProxy = class _DevProxy {
|
|
|
1752
1920
|
if (clientId) {
|
|
1753
1921
|
const client = this.clients.remove(clientId);
|
|
1754
1922
|
if (client?.activeCommandId) {
|
|
1755
|
-
this.rejectPendingCommand(
|
|
1923
|
+
this.rejectPendingCommand(
|
|
1924
|
+
client.activeCommandId,
|
|
1925
|
+
"Browser disconnected during command execution"
|
|
1926
|
+
);
|
|
1756
1927
|
}
|
|
1757
1928
|
}
|
|
1758
1929
|
});
|
|
@@ -1762,12 +1933,16 @@ var DevProxy = class _DevProxy {
|
|
|
1762
1933
|
handleCommandResult(msg) {
|
|
1763
1934
|
const id = msg.id;
|
|
1764
1935
|
if (!id) {
|
|
1765
|
-
log.warn("Browser command result received with no id");
|
|
1936
|
+
log.warn("proxy", "Browser command result received with no id");
|
|
1766
1937
|
return;
|
|
1767
1938
|
}
|
|
1768
1939
|
const pending2 = this.pendingResults.get(id);
|
|
1769
1940
|
if (pending2) {
|
|
1770
|
-
log.info("Browser command result received", {
|
|
1941
|
+
log.info("proxy", "Browser command result received", {
|
|
1942
|
+
id,
|
|
1943
|
+
stepCount: msg.steps?.length,
|
|
1944
|
+
duration: msg.duration
|
|
1945
|
+
});
|
|
1771
1946
|
clearTimeout(pending2.timeout);
|
|
1772
1947
|
this.pendingResults.delete(id);
|
|
1773
1948
|
const client = this.clients.findByCommandId(id);
|
|
@@ -1775,7 +1950,11 @@ var DevProxy = class _DevProxy {
|
|
|
1775
1950
|
pending2.resolve(msg);
|
|
1776
1951
|
this.drainCommandQueue();
|
|
1777
1952
|
} else {
|
|
1778
|
-
log.warn(
|
|
1953
|
+
log.warn(
|
|
1954
|
+
"proxy",
|
|
1955
|
+
"Browser command result received but no pending command found",
|
|
1956
|
+
{ id, pendingIds: [...this.pendingResults.keys()] }
|
|
1957
|
+
);
|
|
1779
1958
|
}
|
|
1780
1959
|
}
|
|
1781
1960
|
rejectPendingCommand(commandId, reason) {
|
|
@@ -1784,7 +1963,7 @@ var DevProxy = class _DevProxy {
|
|
|
1784
1963
|
clearTimeout(pending2.timeout);
|
|
1785
1964
|
this.pendingResults.delete(commandId);
|
|
1786
1965
|
pending2.resolve({ id: commandId, steps: [], error: reason });
|
|
1787
|
-
log.warn("Pending command rejected", { id: commandId, reason });
|
|
1966
|
+
log.warn("proxy", "Pending command rejected", { id: commandId, reason });
|
|
1788
1967
|
this.drainCommandQueue();
|
|
1789
1968
|
}
|
|
1790
1969
|
}
|
|
@@ -1796,7 +1975,10 @@ var DevProxy = class _DevProxy {
|
|
|
1796
1975
|
const removed = this.clients.sweepDead();
|
|
1797
1976
|
for (const { activeCommandId } of removed) {
|
|
1798
1977
|
if (activeCommandId) {
|
|
1799
|
-
this.rejectPendingCommand(
|
|
1978
|
+
this.rejectPendingCommand(
|
|
1979
|
+
activeCommandId,
|
|
1980
|
+
"Browser client timed out"
|
|
1981
|
+
);
|
|
1800
1982
|
}
|
|
1801
1983
|
}
|
|
1802
1984
|
this.clients.pingAll();
|
|
@@ -1820,7 +2002,7 @@ var DevProxy = class _DevProxy {
|
|
|
1820
2002
|
markUpstreamDown() {
|
|
1821
2003
|
if (!this.upstreamUp) return;
|
|
1822
2004
|
this.upstreamUp = false;
|
|
1823
|
-
log.info("Upstream dev server marked as down (explicit signal)");
|
|
2005
|
+
log.info("proxy", "Upstream dev server marked as down (explicit signal)");
|
|
1824
2006
|
this.scheduleHealthCheck(_DevProxy.HEALTH_CHECK_INTERVAL_DOWN);
|
|
1825
2007
|
}
|
|
1826
2008
|
startHealthCheck() {
|
|
@@ -1847,9 +2029,9 @@ var DevProxy = class _DevProxy {
|
|
|
1847
2029
|
this.upstreamUp = false;
|
|
1848
2030
|
}
|
|
1849
2031
|
if (wasUp && !this.upstreamUp) {
|
|
1850
|
-
log.warn("Upstream dev server is down");
|
|
2032
|
+
log.warn("proxy", "Upstream dev server is down");
|
|
1851
2033
|
} else if (!wasUp && this.upstreamUp) {
|
|
1852
|
-
log.info("Upstream dev server is back up, reloading browser");
|
|
2034
|
+
log.info("proxy", "Upstream dev server is back up, reloading browser");
|
|
1853
2035
|
this.broadcastToClients("reload");
|
|
1854
2036
|
}
|
|
1855
2037
|
const interval = this.upstreamUp ? _DevProxy.HEALTH_CHECK_INTERVAL : _DevProxy.HEALTH_CHECK_INTERVAL_DOWN;
|
|
@@ -1879,6 +2061,10 @@ var DevProxy = class _DevProxy {
|
|
|
1879
2061
|
this.handleFontProxy(clientReq, clientRes);
|
|
1880
2062
|
return;
|
|
1881
2063
|
}
|
|
2064
|
+
if (clientReq.url === "/__mindstudio_dev__/mirror" && clientReq.method === "GET") {
|
|
2065
|
+
this.serveMirrorPage(clientRes);
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
1882
2068
|
}
|
|
1883
2069
|
if (clientReq.method === "OPTIONS" && clientReq.headers.origin) {
|
|
1884
2070
|
clientRes.writeHead(204, {
|
|
@@ -1902,7 +2088,10 @@ var DevProxy = class _DevProxy {
|
|
|
1902
2088
|
port: this.upstreamPort,
|
|
1903
2089
|
path: clientReq.url,
|
|
1904
2090
|
method: clientReq.method,
|
|
1905
|
-
headers: {
|
|
2091
|
+
headers: {
|
|
2092
|
+
...clientReq.headers,
|
|
2093
|
+
host: `localhost:${this.upstreamPort}`
|
|
2094
|
+
}
|
|
1906
2095
|
},
|
|
1907
2096
|
(upstreamRes) => {
|
|
1908
2097
|
const contentType = upstreamRes.headers["content-type"] ?? "";
|
|
@@ -1921,7 +2110,6 @@ var DevProxy = class _DevProxy {
|
|
|
1921
2110
|
};
|
|
1922
2111
|
delete headers["content-encoding"];
|
|
1923
2112
|
delete headers["etag"];
|
|
1924
|
-
log.debug("Dev proxy injected context into HTML", { path: clientReq.url, size: html.length });
|
|
1925
2113
|
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1926
2114
|
clientRes.end(html);
|
|
1927
2115
|
});
|
|
@@ -1938,7 +2126,10 @@ var DevProxy = class _DevProxy {
|
|
|
1938
2126
|
}
|
|
1939
2127
|
);
|
|
1940
2128
|
upstreamReq.on("error", (err) => {
|
|
1941
|
-
log.warn("Dev proxy cannot reach dev server", {
|
|
2129
|
+
log.warn("proxy", "Dev proxy cannot reach dev server", {
|
|
2130
|
+
path: clientReq.url,
|
|
2131
|
+
error: err.message
|
|
2132
|
+
});
|
|
1942
2133
|
clientRes.writeHead(502);
|
|
1943
2134
|
clientRes.end(`Proxy error: ${err.message}`);
|
|
1944
2135
|
});
|
|
@@ -1964,6 +2155,91 @@ var DevProxy = class _DevProxy {
|
|
|
1964
2155
|
clientRes.end();
|
|
1965
2156
|
});
|
|
1966
2157
|
}
|
|
2158
|
+
/** Relay a raw mirror message (already JSON-stringified) to all mirror viewers. */
|
|
2159
|
+
relayMirrorEvents(raw) {
|
|
2160
|
+
const mirrors = this.clients.getMirrorClients();
|
|
2161
|
+
for (const client of mirrors) {
|
|
2162
|
+
try {
|
|
2163
|
+
client.ws.send(raw);
|
|
2164
|
+
} catch {
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
/** Serve the mirror replay page — an rrweb Replayer in live mode. */
|
|
2169
|
+
serveMirrorPage(res) {
|
|
2170
|
+
const html = `<!DOCTYPE html>
|
|
2171
|
+
<html>
|
|
2172
|
+
<head>
|
|
2173
|
+
<meta charset="utf-8">
|
|
2174
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2175
|
+
<title>Mobile Mirror</title>
|
|
2176
|
+
<style>
|
|
2177
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2178
|
+
html, body { height: 100%; background: #111; overflow: hidden; }
|
|
2179
|
+
#player { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
|
|
2180
|
+
.replayer-wrapper { box-shadow: 0 0 40px rgba(0,0,0,0.5); border-radius: 4px; overflow: hidden; }
|
|
2181
|
+
#status { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); color: #666; font-family: -apple-system, system-ui, sans-serif; font-size: 13px; }
|
|
2182
|
+
</style>
|
|
2183
|
+
</head>
|
|
2184
|
+
<body>
|
|
2185
|
+
<div id="player"></div>
|
|
2186
|
+
<div id="status">Waiting for mobile device...</div>
|
|
2187
|
+
<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.13/dist/rrweb.umd.cjs.js"></script>
|
|
2188
|
+
<script>
|
|
2189
|
+
(function() {
|
|
2190
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
2191
|
+
var ws = new WebSocket(proto + '//' + location.host + '/__mindstudio_dev__/ws');
|
|
2192
|
+
var replayer = null;
|
|
2193
|
+
var status = document.getElementById('status');
|
|
2194
|
+
|
|
2195
|
+
ws.onopen = function() {
|
|
2196
|
+
ws.send(JSON.stringify({
|
|
2197
|
+
type: 'hello',
|
|
2198
|
+
mode: 'mirror',
|
|
2199
|
+
url: location.href,
|
|
2200
|
+
viewport: { w: window.innerWidth, h: window.innerHeight }
|
|
2201
|
+
}));
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
ws.onmessage = function(e) {
|
|
2205
|
+
var msg;
|
|
2206
|
+
try { msg = JSON.parse(e.data); } catch(e) { return; }
|
|
2207
|
+
|
|
2208
|
+
if (msg.type !== 'mirror' || !Array.isArray(msg.events)) return;
|
|
2209
|
+
|
|
2210
|
+
for (var i = 0; i < msg.events.length; i++) {
|
|
2211
|
+
var event = msg.events[i];
|
|
2212
|
+
if (!replayer) {
|
|
2213
|
+
replayer = new rrweb.Replayer([], {
|
|
2214
|
+
root: document.getElementById('player'),
|
|
2215
|
+
liveMode: true,
|
|
2216
|
+
insertStyleRules: [
|
|
2217
|
+
'.replayer-wrapper { position: relative !important; }',
|
|
2218
|
+
],
|
|
2219
|
+
});
|
|
2220
|
+
replayer.startLive(Date.now() - 500);
|
|
2221
|
+
status.textContent = 'Connected';
|
|
2222
|
+
setTimeout(function() { status.style.opacity = '0'; }, 2000);
|
|
2223
|
+
}
|
|
2224
|
+
replayer.addEvent(event);
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
|
|
2228
|
+
ws.onclose = function() {
|
|
2229
|
+
status.style.opacity = '1';
|
|
2230
|
+
status.textContent = 'Disconnected \u2014 reconnecting...';
|
|
2231
|
+
setTimeout(function() { location.reload(); }, 2000);
|
|
2232
|
+
};
|
|
2233
|
+
})();
|
|
2234
|
+
</script>
|
|
2235
|
+
</body>
|
|
2236
|
+
</html>`;
|
|
2237
|
+
res.writeHead(200, {
|
|
2238
|
+
"content-type": "text/html; charset=utf-8",
|
|
2239
|
+
"cache-control": "no-store"
|
|
2240
|
+
});
|
|
2241
|
+
res.end(html);
|
|
2242
|
+
}
|
|
1967
2243
|
/**
|
|
1968
2244
|
* Proxy a cross-origin font stylesheet or font file through our server,
|
|
1969
2245
|
* adding CORS headers so the browser agent can read the @font-face rules.
|
|
@@ -2005,7 +2281,9 @@ var DevProxy = class _DevProxy {
|
|
|
2005
2281
|
clientRes.end(body);
|
|
2006
2282
|
} catch (err) {
|
|
2007
2283
|
clientRes.writeHead(502, cors);
|
|
2008
|
-
clientRes.end(
|
|
2284
|
+
clientRes.end(
|
|
2285
|
+
`Font proxy error: ${err instanceof Error ? err.message : String(err)}`
|
|
2286
|
+
);
|
|
2009
2287
|
}
|
|
2010
2288
|
}
|
|
2011
2289
|
/**
|
|
@@ -2027,7 +2305,6 @@ ${agentScript}`;
|
|
|
2027
2305
|
// Upstream WebSocket forwarding (HMR etc.)
|
|
2028
2306
|
// ---------------------------------------------------------------------------
|
|
2029
2307
|
handleUpstreamUpgrade(clientReq, clientSocket, head) {
|
|
2030
|
-
log.debug("Dev proxy WebSocket upgrade (upstream)", { path: clientReq.url });
|
|
2031
2308
|
const options = {
|
|
2032
2309
|
hostname: "127.0.0.1",
|
|
2033
2310
|
port: this.upstreamPort,
|
|
@@ -2066,16 +2343,13 @@ ${agentScript}`;
|
|
|
2066
2343
|
};
|
|
2067
2344
|
|
|
2068
2345
|
// src/dev/config/app-config.ts
|
|
2069
|
-
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
2070
|
-
import { join as
|
|
2346
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
2347
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
2071
2348
|
function detectAppConfig(cwd = process.cwd()) {
|
|
2072
|
-
const appJsonPath =
|
|
2073
|
-
if (!existsSync2(appJsonPath))
|
|
2074
|
-
log.debug("mindstudio.json not found", { path: appJsonPath });
|
|
2075
|
-
return null;
|
|
2076
|
-
}
|
|
2349
|
+
const appJsonPath = join6(cwd, "mindstudio.json");
|
|
2350
|
+
if (!existsSync2(appJsonPath)) return null;
|
|
2077
2351
|
try {
|
|
2078
|
-
const raw =
|
|
2352
|
+
const raw = readFileSync2(appJsonPath, "utf-8");
|
|
2079
2353
|
const parsed = JSON.parse(raw);
|
|
2080
2354
|
if (!parsed.name || !Array.isArray(parsed.methods)) {
|
|
2081
2355
|
return null;
|
|
@@ -2090,7 +2364,7 @@ function detectAppConfig(cwd = process.cwd()) {
|
|
|
2090
2364
|
scenarios: parsed.scenarios ?? [],
|
|
2091
2365
|
interfaces: parsed.interfaces ?? []
|
|
2092
2366
|
};
|
|
2093
|
-
log.info("Loaded mindstudio.json", {
|
|
2367
|
+
log.info("config", "Loaded mindstudio.json", {
|
|
2094
2368
|
appId: config2.appId,
|
|
2095
2369
|
roles: config2.roles.length,
|
|
2096
2370
|
methods: config2.methods.length,
|
|
@@ -2100,7 +2374,7 @@ function detectAppConfig(cwd = process.cwd()) {
|
|
|
2100
2374
|
});
|
|
2101
2375
|
return config2;
|
|
2102
2376
|
} catch (err) {
|
|
2103
|
-
log.warn("Failed to parse mindstudio.json", { error: err instanceof Error ? err.message : String(err) });
|
|
2377
|
+
log.warn("config", "Failed to parse mindstudio.json", { error: err instanceof Error ? err.message : String(err) });
|
|
2104
2378
|
return null;
|
|
2105
2379
|
}
|
|
2106
2380
|
}
|
|
@@ -2111,12 +2385,12 @@ function getWebInterfaceConfig(appConfig, cwd = process.cwd()) {
|
|
|
2111
2385
|
if (!webInterface) {
|
|
2112
2386
|
return null;
|
|
2113
2387
|
}
|
|
2114
|
-
const configPath =
|
|
2388
|
+
const configPath = join6(cwd, webInterface.path);
|
|
2115
2389
|
if (!existsSync2(configPath)) {
|
|
2116
2390
|
return null;
|
|
2117
2391
|
}
|
|
2118
2392
|
try {
|
|
2119
|
-
const raw =
|
|
2393
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
2120
2394
|
const parsed = JSON.parse(raw);
|
|
2121
2395
|
const web = parsed.web;
|
|
2122
2396
|
if (!web || typeof web !== "object") {
|
|
@@ -2137,26 +2411,26 @@ function getWebProjectDir(appConfig, cwd = process.cwd()) {
|
|
|
2137
2411
|
if (!webInterface) {
|
|
2138
2412
|
return null;
|
|
2139
2413
|
}
|
|
2140
|
-
return
|
|
2414
|
+
return dirname3(join6(cwd, webInterface.path));
|
|
2141
2415
|
}
|
|
2142
2416
|
function readTableSources(appConfig, cwd = process.cwd()) {
|
|
2143
2417
|
const results = [];
|
|
2144
2418
|
for (const table of appConfig.tables) {
|
|
2145
|
-
const filePath =
|
|
2419
|
+
const filePath = join6(cwd, table.path);
|
|
2146
2420
|
if (!existsSync2(filePath)) {
|
|
2147
|
-
log.warn("Table source file not found", { table: table.export, path: table.path });
|
|
2421
|
+
log.warn("config", "Table source file not found", { table: table.export, path: table.path });
|
|
2148
2422
|
continue;
|
|
2149
2423
|
}
|
|
2150
2424
|
try {
|
|
2151
|
-
const source =
|
|
2425
|
+
const source = readFileSync2(filePath, "utf-8");
|
|
2152
2426
|
const name = table.export;
|
|
2153
2427
|
results.push({ name, source });
|
|
2154
2428
|
} catch (err) {
|
|
2155
|
-
log.warn("Table source file unreadable", { table: table.export, path: table.path, error: err instanceof Error ? err.message : String(err) });
|
|
2429
|
+
log.warn("config", "Table source file unreadable", { table: table.export, path: table.path, error: err instanceof Error ? err.message : String(err) });
|
|
2156
2430
|
}
|
|
2157
2431
|
}
|
|
2158
2432
|
if (results.length < appConfig.tables.length) {
|
|
2159
|
-
log.warn("
|
|
2433
|
+
log.warn("config", "Table source files missing", { missing: appConfig.tables.length - results.length, found: results.length, expected: appConfig.tables.length });
|
|
2160
2434
|
}
|
|
2161
2435
|
return results;
|
|
2162
2436
|
}
|
|
@@ -2166,9 +2440,9 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
2166
2440
|
const firstMethodPath = appConfig.methods[0].path;
|
|
2167
2441
|
const parts = firstMethodPath.split("/");
|
|
2168
2442
|
for (let i = parts.length - 1; i >= 1; i--) {
|
|
2169
|
-
const candidate =
|
|
2170
|
-
if (existsSync2(
|
|
2171
|
-
if (!existsSync2(
|
|
2443
|
+
const candidate = join6(cwd, ...parts.slice(0, i));
|
|
2444
|
+
if (existsSync2(join6(candidate, "package.json"))) {
|
|
2445
|
+
if (!existsSync2(join6(candidate, "node_modules"))) {
|
|
2172
2446
|
dirs.push(candidate);
|
|
2173
2447
|
}
|
|
2174
2448
|
break;
|
|
@@ -2176,8 +2450,8 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
2176
2450
|
}
|
|
2177
2451
|
}
|
|
2178
2452
|
const webProjectDir = getWebProjectDir(appConfig, cwd);
|
|
2179
|
-
if (webProjectDir && existsSync2(
|
|
2180
|
-
if (!existsSync2(
|
|
2453
|
+
if (webProjectDir && existsSync2(join6(webProjectDir, "package.json"))) {
|
|
2454
|
+
if (!existsSync2(join6(webProjectDir, "node_modules"))) {
|
|
2181
2455
|
dirs.push(webProjectDir);
|
|
2182
2456
|
}
|
|
2183
2457
|
}
|
|
@@ -2206,11 +2480,11 @@ function detectGitBranch() {
|
|
|
2206
2480
|
|
|
2207
2481
|
// src/dev/config/table-watcher.ts
|
|
2208
2482
|
import { watch } from "chokidar";
|
|
2209
|
-
import { join as
|
|
2483
|
+
import { join as join7, dirname as dirname4, basename as basename2 } from "path";
|
|
2210
2484
|
function watchTableFiles(tables, cwd, onChanged) {
|
|
2211
2485
|
if (tables.length === 0) return () => {
|
|
2212
2486
|
};
|
|
2213
|
-
const filePaths = tables.map((t) =>
|
|
2487
|
+
const filePaths = tables.map((t) => join7(cwd, t.path));
|
|
2214
2488
|
let syncTimer;
|
|
2215
2489
|
const watcher = watch(filePaths, {
|
|
2216
2490
|
ignoreInitial: true,
|
|
@@ -2223,13 +2497,13 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
2223
2497
|
});
|
|
2224
2498
|
const dirToFiles = /* @__PURE__ */ new Map();
|
|
2225
2499
|
for (const table of tables) {
|
|
2226
|
-
const absPath =
|
|
2227
|
-
const dir =
|
|
2500
|
+
const absPath = join7(cwd, table.path);
|
|
2501
|
+
const dir = dirname4(absPath);
|
|
2228
2502
|
const file = basename2(absPath);
|
|
2229
2503
|
if (!dirToFiles.has(dir)) dirToFiles.set(dir, /* @__PURE__ */ new Set());
|
|
2230
2504
|
dirToFiles.get(dir).add(file);
|
|
2231
2505
|
}
|
|
2232
|
-
log.info("Watching table source files", {
|
|
2506
|
+
log.info("config", "Watching table source files", {
|
|
2233
2507
|
dirs: dirToFiles.size,
|
|
2234
2508
|
tables: tables.length
|
|
2235
2509
|
});
|
|
@@ -2241,9 +2515,9 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
2241
2515
|
|
|
2242
2516
|
// src/dev/config/config-watcher.ts
|
|
2243
2517
|
import { watch as watch2 } from "chokidar";
|
|
2244
|
-
import { join as
|
|
2518
|
+
import { join as join8 } from "path";
|
|
2245
2519
|
function watchConfigFile(cwd, onChanged) {
|
|
2246
|
-
const configPath =
|
|
2520
|
+
const configPath = join8(cwd, "mindstudio.json");
|
|
2247
2521
|
let debounceTimer;
|
|
2248
2522
|
const watcher = watch2(configPath, {
|
|
2249
2523
|
ignoreInitial: true,
|
|
@@ -2255,7 +2529,7 @@ function watchConfigFile(cwd, onChanged) {
|
|
|
2255
2529
|
onChanged();
|
|
2256
2530
|
}, 500);
|
|
2257
2531
|
});
|
|
2258
|
-
log.info("Watching mindstudio.json for changes", { path: configPath });
|
|
2532
|
+
log.info("config", "Watching mindstudio.json for changes", { path: configPath });
|
|
2259
2533
|
return () => {
|
|
2260
2534
|
clearTimeout(debounceTimer);
|
|
2261
2535
|
watcher.close();
|
|
@@ -2310,4 +2584,4 @@ export {
|
|
|
2310
2584
|
watchTableFiles,
|
|
2311
2585
|
watchConfigFile
|
|
2312
2586
|
};
|
|
2313
|
-
//# sourceMappingURL=chunk-
|
|
2587
|
+
//# sourceMappingURL=chunk-WECAPRKN.js.map
|