@mindstudio-ai/local-model-tunnel 0.5.10 → 0.5.12
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-WVEIWPL3.js → chunk-U2KITVPK.js} +2 -2
- package/dist/{chunk-C4IDR7TD.js → chunk-XP4GPID6.js} +611 -152
- package/dist/chunk-XP4GPID6.js.map +1 -0
- package/dist/chunk-YYWJE5O6.js +500 -0
- package/dist/chunk-YYWJE5O6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/headless.d.ts +2 -66
- package/dist/headless.js +2 -2
- package/dist/index.js +3 -3
- package/dist/{tui-4CPF4HWT.js → tui-BDBV3AWJ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-C4IDR7TD.js.map +0 -1
- package/dist/chunk-ISB2N2SP.js +0 -368
- package/dist/chunk-ISB2N2SP.js.map +0 -1
- /package/dist/{chunk-WVEIWPL3.js.map → chunk-U2KITVPK.js.map} +0 -0
- /package/dist/{tui-4CPF4HWT.js.map → tui-BDBV3AWJ.js.map} +0 -0
|
@@ -266,6 +266,147 @@ var DevPollError = class extends Error {
|
|
|
266
266
|
}
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
// src/dev/ndjson-log.ts
|
|
270
|
+
import fs2 from "fs";
|
|
271
|
+
import { join } from "path";
|
|
272
|
+
var NdjsonLog = class {
|
|
273
|
+
constructor(filename, maxLines = 500, keepLines = 300, maxBytes = 2 * 1024 * 1024) {
|
|
274
|
+
this.filename = filename;
|
|
275
|
+
this.maxLines = maxLines;
|
|
276
|
+
this.keepLines = keepLines;
|
|
277
|
+
this.maxBytes = maxBytes;
|
|
278
|
+
}
|
|
279
|
+
fd = null;
|
|
280
|
+
logPath = null;
|
|
281
|
+
lineCount = 0;
|
|
282
|
+
rotating = false;
|
|
283
|
+
init(projectRoot) {
|
|
284
|
+
this.close();
|
|
285
|
+
try {
|
|
286
|
+
const logsDir = join(projectRoot, ".logs");
|
|
287
|
+
fs2.mkdirSync(logsDir, { recursive: true });
|
|
288
|
+
this.logPath = join(logsDir, this.filename);
|
|
289
|
+
if (fs2.existsSync(this.logPath)) {
|
|
290
|
+
const content = fs2.readFileSync(this.logPath, "utf-8");
|
|
291
|
+
this.lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
292
|
+
} else {
|
|
293
|
+
this.lineCount = 0;
|
|
294
|
+
}
|
|
295
|
+
this.fd = fs2.openSync(this.logPath, "a");
|
|
296
|
+
log.info(`${this.filename} log initialized`, {
|
|
297
|
+
path: this.logPath,
|
|
298
|
+
existingEntries: this.lineCount
|
|
299
|
+
});
|
|
300
|
+
} catch (err) {
|
|
301
|
+
log.warn(`Failed to initialize ${this.filename} log`, {
|
|
302
|
+
error: err instanceof Error ? err.message : String(err)
|
|
303
|
+
});
|
|
304
|
+
this.fd = null;
|
|
305
|
+
this.logPath = null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
append(record) {
|
|
309
|
+
if (this.fd === null) return;
|
|
310
|
+
try {
|
|
311
|
+
const line = JSON.stringify(record) + "\n";
|
|
312
|
+
fs2.writeSync(this.fd, line);
|
|
313
|
+
this.lineCount++;
|
|
314
|
+
this.maybeRotate();
|
|
315
|
+
} catch (err) {
|
|
316
|
+
log.debug(`Failed to write ${this.filename} log entry`, {
|
|
317
|
+
error: err instanceof Error ? err.message : String(err)
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
close() {
|
|
322
|
+
if (this.fd !== null) {
|
|
323
|
+
try {
|
|
324
|
+
fs2.closeSync(this.fd);
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
this.fd = null;
|
|
328
|
+
}
|
|
329
|
+
this.logPath = null;
|
|
330
|
+
this.lineCount = 0;
|
|
331
|
+
this.rotating = false;
|
|
332
|
+
}
|
|
333
|
+
maybeRotate() {
|
|
334
|
+
if (this.fd === null || this.logPath === null || this.rotating) return;
|
|
335
|
+
try {
|
|
336
|
+
let needsRotation = this.lineCount > this.maxLines;
|
|
337
|
+
if (!needsRotation) {
|
|
338
|
+
const stat = fs2.fstatSync(this.fd);
|
|
339
|
+
needsRotation = stat.size > this.maxBytes;
|
|
340
|
+
}
|
|
341
|
+
if (!needsRotation) return;
|
|
342
|
+
this.rotating = true;
|
|
343
|
+
const content = fs2.readFileSync(this.logPath, "utf-8");
|
|
344
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
345
|
+
const kept = lines.slice(-this.keepLines);
|
|
346
|
+
fs2.closeSync(this.fd);
|
|
347
|
+
fs2.writeFileSync(this.logPath, kept.join("\n") + "\n", "utf-8");
|
|
348
|
+
this.fd = fs2.openSync(this.logPath, "a");
|
|
349
|
+
this.lineCount = kept.length;
|
|
350
|
+
log.debug(`${this.filename} log rotated`, { kept: this.lineCount });
|
|
351
|
+
} catch (err) {
|
|
352
|
+
log.debug(`${this.filename} log rotation failed`, {
|
|
353
|
+
error: err instanceof Error ? err.message : String(err)
|
|
354
|
+
});
|
|
355
|
+
} finally {
|
|
356
|
+
this.rotating = false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/dev/request-log.ts
|
|
362
|
+
var ndjsonLog = new NdjsonLog("requests.ndjson");
|
|
363
|
+
function initRequestLog(projectRoot) {
|
|
364
|
+
ndjsonLog.init(projectRoot);
|
|
365
|
+
}
|
|
366
|
+
function logMethodExecution(entry) {
|
|
367
|
+
ndjsonLog.append({
|
|
368
|
+
type: "method",
|
|
369
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
+
requestId: entry.requestId,
|
|
371
|
+
sessionId: entry.sessionId,
|
|
372
|
+
method: entry.methodExport,
|
|
373
|
+
path: entry.methodPath,
|
|
374
|
+
input: entry.input,
|
|
375
|
+
roleOverride: entry.roleOverride ?? null,
|
|
376
|
+
authorizationToken: entry.authorizationToken,
|
|
377
|
+
databases: entry.databases,
|
|
378
|
+
success: entry.result.success,
|
|
379
|
+
output: entry.result.output ?? null,
|
|
380
|
+
error: entry.result.error ?? null,
|
|
381
|
+
stdout: entry.result.stdout ?? [],
|
|
382
|
+
duration: entry.duration,
|
|
383
|
+
stats: entry.result.stats ?? null
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function logScenarioExecution(entry) {
|
|
387
|
+
ndjsonLog.append({
|
|
388
|
+
type: "scenario",
|
|
389
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
390
|
+
sessionId: entry.sessionId,
|
|
391
|
+
scenario: {
|
|
392
|
+
id: entry.scenario.id,
|
|
393
|
+
name: entry.scenario.name ?? entry.scenario.export,
|
|
394
|
+
export: entry.scenario.export,
|
|
395
|
+
path: entry.scenario.path
|
|
396
|
+
},
|
|
397
|
+
databases: entry.databases,
|
|
398
|
+
success: entry.result?.success ?? false,
|
|
399
|
+
output: entry.result?.output ?? null,
|
|
400
|
+
error: entry.result?.error ?? (entry.infrastructureError ? { message: entry.infrastructureError } : null),
|
|
401
|
+
stdout: entry.result?.stdout ?? [],
|
|
402
|
+
duration: entry.duration,
|
|
403
|
+
stats: entry.result?.stats ?? null
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function closeRequestLog() {
|
|
407
|
+
ndjsonLog.close();
|
|
408
|
+
}
|
|
409
|
+
|
|
269
410
|
// src/dev/events.ts
|
|
270
411
|
import { EventEmitter } from "events";
|
|
271
412
|
var DevEventEmitter = class extends EventEmitter {
|
|
@@ -352,7 +493,7 @@ var devRequestEvents = new DevEventEmitter();
|
|
|
352
493
|
// src/dev/transpiler.ts
|
|
353
494
|
import { unlink, mkdir, readdir } from "fs/promises";
|
|
354
495
|
import { existsSync } from "fs";
|
|
355
|
-
import { resolve, dirname, basename, join } from "path";
|
|
496
|
+
import { resolve, dirname, basename, join as join2 } from "path";
|
|
356
497
|
import { build } from "esbuild";
|
|
357
498
|
var Transpiler = class {
|
|
358
499
|
projectRoot;
|
|
@@ -378,18 +519,18 @@ var Transpiler = class {
|
|
|
378
519
|
const start = Date.now();
|
|
379
520
|
const absolutePath = resolve(this.projectRoot, methodPath);
|
|
380
521
|
const name = basename(absolutePath).replace(/\.[^.]+$/, "");
|
|
381
|
-
log.debug("
|
|
522
|
+
log.debug("Transpiling method", { methodPath });
|
|
382
523
|
const nodeModulesDir = findNearestNodeModules(dirname(absolutePath));
|
|
383
524
|
if (!nodeModulesDir) {
|
|
384
|
-
log.error("
|
|
525
|
+
log.error("Cannot find node_modules for method", { methodPath, searchStart: dirname(absolutePath) });
|
|
385
526
|
throw new Error(
|
|
386
527
|
`No node_modules found near ${methodPath}. Run npm install first.`
|
|
387
528
|
);
|
|
388
529
|
}
|
|
389
|
-
log.debug("
|
|
390
|
-
const outDir =
|
|
530
|
+
log.debug("Found node_modules", { path: nodeModulesDir });
|
|
531
|
+
const outDir = join2(nodeModulesDir, ".cache", "mindstudio-dev");
|
|
391
532
|
await mkdir(outDir, { recursive: true });
|
|
392
|
-
const outfile =
|
|
533
|
+
const outfile = join2(outDir, `${name}.__ms_dev__.mjs`);
|
|
393
534
|
await build({
|
|
394
535
|
entryPoints: [absolutePath],
|
|
395
536
|
bundle: true,
|
|
@@ -402,14 +543,14 @@ var Transpiler = class {
|
|
|
402
543
|
logLevel: "silent"
|
|
403
544
|
});
|
|
404
545
|
this.outputFiles.add(outfile);
|
|
405
|
-
log.info(`
|
|
546
|
+
log.info(`Method transpiled in ${Date.now() - start}ms`, { methodPath, outfile });
|
|
406
547
|
return outfile;
|
|
407
548
|
}
|
|
408
549
|
/**
|
|
409
550
|
* Clean up all transpiled output files.
|
|
410
551
|
*/
|
|
411
552
|
async cleanup() {
|
|
412
|
-
log.debug("
|
|
553
|
+
log.debug("Cleaning up transpiled files", { fileCount: this.outputFiles.size });
|
|
413
554
|
for (const file of this.outputFiles) {
|
|
414
555
|
await unlink(file).catch(() => {
|
|
415
556
|
});
|
|
@@ -421,11 +562,11 @@ async function removeOrphanedDevFiles(dir) {
|
|
|
421
562
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
422
563
|
for (const entry of entries) {
|
|
423
564
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
424
|
-
const fullPath =
|
|
565
|
+
const fullPath = join2(dir, entry.name);
|
|
425
566
|
if (entry.isDirectory()) {
|
|
426
567
|
await removeOrphanedDevFiles(fullPath);
|
|
427
568
|
} else if (entry.name.endsWith(".__ms_dev__.mjs")) {
|
|
428
|
-
log.debug("
|
|
569
|
+
log.debug("Removing orphaned transpiled file", { path: fullPath });
|
|
429
570
|
await unlink(fullPath).catch(() => {
|
|
430
571
|
});
|
|
431
572
|
}
|
|
@@ -434,7 +575,7 @@ async function removeOrphanedDevFiles(dir) {
|
|
|
434
575
|
function findNearestNodeModules(startDir) {
|
|
435
576
|
let dir = startDir;
|
|
436
577
|
while (true) {
|
|
437
|
-
const candidate =
|
|
578
|
+
const candidate = join2(dir, "node_modules");
|
|
438
579
|
if (existsSync(candidate)) {
|
|
439
580
|
return candidate;
|
|
440
581
|
}
|
|
@@ -448,7 +589,7 @@ function findNearestNodeModules(startDir) {
|
|
|
448
589
|
// src/dev/executor.ts
|
|
449
590
|
import { fork } from "child_process";
|
|
450
591
|
import { writeFile, unlink as unlink2 } from "fs/promises";
|
|
451
|
-
import { join as
|
|
592
|
+
import { join as join3 } from "path";
|
|
452
593
|
import { tmpdir } from "os";
|
|
453
594
|
import { randomBytes } from "crypto";
|
|
454
595
|
var EXECUTION_TIMEOUT_MS = 3e4;
|
|
@@ -557,14 +698,14 @@ async function ensureWorker(projectRoot) {
|
|
|
557
698
|
});
|
|
558
699
|
workerScriptPath = null;
|
|
559
700
|
}
|
|
560
|
-
const scriptPath =
|
|
701
|
+
const scriptPath = join3(
|
|
561
702
|
tmpdir(),
|
|
562
703
|
`ms-dev-worker-${randomBytes(4).toString("hex")}.mjs`
|
|
563
704
|
);
|
|
564
705
|
await writeFile(scriptPath, buildWorkerScript(), "utf-8");
|
|
565
706
|
workerScriptPath = scriptPath;
|
|
566
707
|
workerProjectRoot = projectRoot;
|
|
567
|
-
log.debug("
|
|
708
|
+
log.debug("Spawning method execution process", { cwd: projectRoot, scriptPath });
|
|
568
709
|
const child = fork(scriptPath, [], {
|
|
569
710
|
cwd: projectRoot,
|
|
570
711
|
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
@@ -590,7 +731,7 @@ async function ensureWorker(projectRoot) {
|
|
|
590
731
|
req.resolve(msg);
|
|
591
732
|
});
|
|
592
733
|
child.on("exit", (code) => {
|
|
593
|
-
log.warn("
|
|
734
|
+
log.warn("Method execution process exited unexpectedly", { code });
|
|
594
735
|
for (const [id, req] of pending) {
|
|
595
736
|
clearTimeout(req.timer);
|
|
596
737
|
req.resolve({ success: false, error: { message: `Worker process exited with code ${code}` } });
|
|
@@ -600,20 +741,20 @@ async function ensureWorker(projectRoot) {
|
|
|
600
741
|
});
|
|
601
742
|
child.stderr?.on("data", (chunk) => {
|
|
602
743
|
const text = chunk.toString().trim();
|
|
603
|
-
if (text) log.
|
|
744
|
+
if (text) log.warn("Method process stderr", { text: text.slice(0, 500) });
|
|
604
745
|
});
|
|
605
746
|
worker = child;
|
|
606
|
-
log.info("
|
|
747
|
+
log.info("Method execution process ready", { pid: child.pid });
|
|
607
748
|
return child;
|
|
608
749
|
}
|
|
609
750
|
async function executeMethod(opts) {
|
|
610
751
|
const w = await ensureWorker(opts.projectRoot);
|
|
611
752
|
const id = randomBytes(8).toString("hex");
|
|
612
|
-
log.debug("
|
|
753
|
+
log.debug("Sending method to execution process", { id, methodExport: opts.methodExport });
|
|
613
754
|
return new Promise((resolve2) => {
|
|
614
755
|
const timer = setTimeout(() => {
|
|
615
756
|
pending.delete(id);
|
|
616
|
-
log.warn("
|
|
757
|
+
log.warn("Method execution timed out", { id, methodExport: opts.methodExport });
|
|
617
758
|
resolve2({
|
|
618
759
|
success: false,
|
|
619
760
|
error: { message: "Method execution timed out after 30s" }
|
|
@@ -811,6 +952,33 @@ async function disconnectHeartbeat() {
|
|
|
811
952
|
}
|
|
812
953
|
}
|
|
813
954
|
|
|
955
|
+
// src/dev/runner.ts
|
|
956
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
957
|
+
|
|
958
|
+
// src/dev/format-error.ts
|
|
959
|
+
function formatErrorForDisplay(error) {
|
|
960
|
+
const parts = [];
|
|
961
|
+
if (error.message) {
|
|
962
|
+
parts.push(String(error.message));
|
|
963
|
+
}
|
|
964
|
+
const code = error.code ?? error.statusCode ?? error.status;
|
|
965
|
+
if (code !== void 0) {
|
|
966
|
+
parts.push(`(code: ${code})`);
|
|
967
|
+
}
|
|
968
|
+
if (error.body) {
|
|
969
|
+
parts.push(`Response: ${String(error.body).slice(0, 200)}`);
|
|
970
|
+
} else if (error.response) {
|
|
971
|
+
parts.push(`Response: ${String(error.response).slice(0, 200)}`);
|
|
972
|
+
}
|
|
973
|
+
if (error.cause && typeof error.cause === "object") {
|
|
974
|
+
const cause = error.cause;
|
|
975
|
+
if (cause.message) {
|
|
976
|
+
parts.push(`Caused by: ${cause.message}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return parts.join("\n");
|
|
980
|
+
}
|
|
981
|
+
|
|
814
982
|
// src/dev/runner.ts
|
|
815
983
|
var DevRunner = class {
|
|
816
984
|
constructor(appId, projectRoot, startOpts = {}) {
|
|
@@ -839,24 +1007,24 @@ var DevRunner = class {
|
|
|
839
1007
|
if (this.isRunning) {
|
|
840
1008
|
throw new Error("DevRunner is already running");
|
|
841
1009
|
}
|
|
842
|
-
log.info("
|
|
1010
|
+
log.info("Dev session starting", { appId: this.appId, branch: this.startOpts.branch });
|
|
843
1011
|
const session = await startDevSession(this.appId, this.startOpts);
|
|
844
1012
|
this.session = session;
|
|
845
1013
|
this.transpiler = new Transpiler(this.projectRoot);
|
|
846
1014
|
this.isRunning = true;
|
|
847
1015
|
this.backoffMs = 1e3;
|
|
848
|
-
log.info("
|
|
1016
|
+
log.info("Dev session started", { sessionId: session.sessionId, branch: session.branch });
|
|
849
1017
|
this.pollLoop();
|
|
850
1018
|
return session;
|
|
851
1019
|
}
|
|
852
1020
|
async stop() {
|
|
853
|
-
log.info("
|
|
1021
|
+
log.info("Dev session stopping");
|
|
854
1022
|
this.isRunning = false;
|
|
855
1023
|
if (this.session) {
|
|
856
1024
|
try {
|
|
857
1025
|
await stopDevSession(this.appId, this.session.sessionId);
|
|
858
1026
|
} catch (err) {
|
|
859
|
-
log.warn("
|
|
1027
|
+
log.warn("Failed to stop dev session cleanly", { error: err instanceof Error ? err.message : String(err) });
|
|
860
1028
|
}
|
|
861
1029
|
this.session = null;
|
|
862
1030
|
}
|
|
@@ -872,7 +1040,7 @@ var DevRunner = class {
|
|
|
872
1040
|
// Set role override for subsequent method executions.
|
|
873
1041
|
async setImpersonation(roles) {
|
|
874
1042
|
if (!this.session) return;
|
|
875
|
-
log.info("
|
|
1043
|
+
log.info("Setting role override", { roles });
|
|
876
1044
|
const result = await impersonate(this.appId, this.session.sessionId, roles);
|
|
877
1045
|
await this.refreshClientContext();
|
|
878
1046
|
devRequestEvents.emitImpersonate({ roles: result.roles });
|
|
@@ -880,7 +1048,7 @@ var DevRunner = class {
|
|
|
880
1048
|
// Clear role override — revert to session's default roles.
|
|
881
1049
|
async clearImpersonation() {
|
|
882
1050
|
if (!this.session) return;
|
|
883
|
-
log.info("
|
|
1051
|
+
log.info("Clearing role override");
|
|
884
1052
|
const result = await impersonate(this.appId, this.session.sessionId, null);
|
|
885
1053
|
await this.refreshClientContext();
|
|
886
1054
|
devRequestEvents.emitImpersonate({ roles: result.roles });
|
|
@@ -894,7 +1062,94 @@ var DevRunner = class {
|
|
|
894
1062
|
this.session.clientContext = context;
|
|
895
1063
|
this.proxy.updateClientContext(context);
|
|
896
1064
|
} catch (err) {
|
|
897
|
-
log.warn("
|
|
1065
|
+
log.warn("Failed to refresh session context after role change", { error: err instanceof Error ? err.message : String(err) });
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
// Run a method directly (not via poll loop). Used by headless stdin commands
|
|
1069
|
+
// and programmatic callers to test methods without a browser.
|
|
1070
|
+
async runMethod(opts) {
|
|
1071
|
+
if (!this.session || !this.transpiler) {
|
|
1072
|
+
return { success: false, error: { message: "Session not started" }, duration: 0 };
|
|
1073
|
+
}
|
|
1074
|
+
const requestId = randomBytes2(8).toString("hex");
|
|
1075
|
+
const startTime = Date.now();
|
|
1076
|
+
devRequestEvents.emitStart({
|
|
1077
|
+
id: requestId,
|
|
1078
|
+
type: "execute",
|
|
1079
|
+
method: opts.methodExport,
|
|
1080
|
+
timestamp: startTime
|
|
1081
|
+
});
|
|
1082
|
+
log.info("Method received (direct)", { requestId, method: opts.methodExport });
|
|
1083
|
+
try {
|
|
1084
|
+
const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
|
|
1085
|
+
const transpiledPath = await this.transpiler.transpile(opts.methodPath);
|
|
1086
|
+
const result = await executeMethod({
|
|
1087
|
+
transpiledPath,
|
|
1088
|
+
methodExport: opts.methodExport,
|
|
1089
|
+
input: opts.input,
|
|
1090
|
+
auth: this.session.auth,
|
|
1091
|
+
databases: this.session.databases,
|
|
1092
|
+
authorizationToken,
|
|
1093
|
+
apiBaseUrl: getApiBaseUrl(),
|
|
1094
|
+
projectRoot: this.projectRoot
|
|
1095
|
+
});
|
|
1096
|
+
const duration = Date.now() - startTime;
|
|
1097
|
+
if (result.success) {
|
|
1098
|
+
log.info("Method complete", { requestId, method: opts.methodExport, duration });
|
|
1099
|
+
} else {
|
|
1100
|
+
log.warn("Method failed", {
|
|
1101
|
+
requestId,
|
|
1102
|
+
method: opts.methodExport,
|
|
1103
|
+
duration,
|
|
1104
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
logMethodExecution({
|
|
1108
|
+
requestId,
|
|
1109
|
+
sessionId: this.session.sessionId,
|
|
1110
|
+
methodExport: opts.methodExport,
|
|
1111
|
+
methodPath: opts.methodPath,
|
|
1112
|
+
input: opts.input,
|
|
1113
|
+
authorizationToken,
|
|
1114
|
+
databases: this.session.databases,
|
|
1115
|
+
result,
|
|
1116
|
+
duration
|
|
1117
|
+
});
|
|
1118
|
+
devRequestEvents.emitComplete({
|
|
1119
|
+
id: requestId,
|
|
1120
|
+
success: result.success,
|
|
1121
|
+
duration,
|
|
1122
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1123
|
+
});
|
|
1124
|
+
return {
|
|
1125
|
+
success: result.success,
|
|
1126
|
+
output: result.output,
|
|
1127
|
+
error: result.error ?? null,
|
|
1128
|
+
stdout: result.stdout,
|
|
1129
|
+
duration
|
|
1130
|
+
};
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1133
|
+
const duration = Date.now() - startTime;
|
|
1134
|
+
log.error("Method error", { requestId, method: opts.methodExport, duration, error: message });
|
|
1135
|
+
logMethodExecution({
|
|
1136
|
+
requestId,
|
|
1137
|
+
sessionId: this.session.sessionId,
|
|
1138
|
+
methodExport: opts.methodExport,
|
|
1139
|
+
methodPath: opts.methodPath,
|
|
1140
|
+
input: opts.input,
|
|
1141
|
+
authorizationToken: "",
|
|
1142
|
+
databases: this.session.databases,
|
|
1143
|
+
result: { success: false, error: { message } },
|
|
1144
|
+
duration
|
|
1145
|
+
});
|
|
1146
|
+
devRequestEvents.emitComplete({
|
|
1147
|
+
id: requestId,
|
|
1148
|
+
success: false,
|
|
1149
|
+
duration,
|
|
1150
|
+
error: message
|
|
1151
|
+
});
|
|
1152
|
+
return { success: false, error: { message }, duration };
|
|
898
1153
|
}
|
|
899
1154
|
}
|
|
900
1155
|
// Run a scenario: truncate tables → execute seed → impersonate roles.
|
|
@@ -910,16 +1165,15 @@ var DevRunner = class {
|
|
|
910
1165
|
name: scenarioName,
|
|
911
1166
|
timestamp: startTime
|
|
912
1167
|
});
|
|
913
|
-
log.info("
|
|
1168
|
+
log.info("Scenario starting", { id: scenario.id, name: scenarioName });
|
|
914
1169
|
try {
|
|
915
|
-
log.
|
|
1170
|
+
log.info("Resetting database for scenario");
|
|
916
1171
|
const databases = await resetDevDatabase(this.appId, this.session.sessionId, "truncate");
|
|
917
1172
|
this.session.databases = databases;
|
|
918
|
-
log.
|
|
1173
|
+
log.info("Transpiling scenario", { path: scenario.path });
|
|
919
1174
|
const transpiledPath = await this.transpiler.transpile(scenario.path);
|
|
920
|
-
log.debug("runner Fetching callback token for scenario");
|
|
921
1175
|
const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
|
|
922
|
-
log.
|
|
1176
|
+
log.info("Running scenario seed function", { export: scenario.export });
|
|
923
1177
|
const result = await executeMethod({
|
|
924
1178
|
transpiledPath,
|
|
925
1179
|
methodExport: scenario.export,
|
|
@@ -932,7 +1186,14 @@ var DevRunner = class {
|
|
|
932
1186
|
});
|
|
933
1187
|
if (!result.success) {
|
|
934
1188
|
const error = result.error?.message ?? "Scenario seed failed";
|
|
935
|
-
log.error("
|
|
1189
|
+
log.error("Scenario seed function failed", { id: scenario.id, error });
|
|
1190
|
+
logScenarioExecution({
|
|
1191
|
+
sessionId: this.session.sessionId,
|
|
1192
|
+
scenario,
|
|
1193
|
+
databases: this.session.databases,
|
|
1194
|
+
result,
|
|
1195
|
+
duration: Date.now() - startTime
|
|
1196
|
+
});
|
|
936
1197
|
devRequestEvents.emitScenarioComplete({
|
|
937
1198
|
id: scenario.id,
|
|
938
1199
|
success: false,
|
|
@@ -943,12 +1204,19 @@ var DevRunner = class {
|
|
|
943
1204
|
return { success: false, databases, error };
|
|
944
1205
|
}
|
|
945
1206
|
if (scenario.roles.length > 0) {
|
|
946
|
-
log.
|
|
1207
|
+
log.info("Setting role override for scenario", { roles: scenario.roles });
|
|
947
1208
|
await impersonate(this.appId, this.session.sessionId, scenario.roles);
|
|
948
1209
|
await this.refreshClientContext();
|
|
949
1210
|
}
|
|
950
1211
|
const duration = Date.now() - startTime;
|
|
951
|
-
log.info("
|
|
1212
|
+
log.info("Scenario complete", { id: scenario.id, duration, roles: scenario.roles });
|
|
1213
|
+
logScenarioExecution({
|
|
1214
|
+
sessionId: this.session.sessionId,
|
|
1215
|
+
scenario,
|
|
1216
|
+
databases: this.session.databases,
|
|
1217
|
+
result,
|
|
1218
|
+
duration
|
|
1219
|
+
});
|
|
952
1220
|
devRequestEvents.emitScenarioComplete({
|
|
953
1221
|
id: scenario.id,
|
|
954
1222
|
success: true,
|
|
@@ -958,7 +1226,15 @@ var DevRunner = class {
|
|
|
958
1226
|
return { success: true, databases };
|
|
959
1227
|
} catch (err) {
|
|
960
1228
|
const error = err instanceof Error ? err.message : "Unknown error";
|
|
961
|
-
log.error("
|
|
1229
|
+
log.error("Scenario failed", { id: scenario.id, error });
|
|
1230
|
+
logScenarioExecution({
|
|
1231
|
+
sessionId: this.session.sessionId,
|
|
1232
|
+
scenario,
|
|
1233
|
+
databases: this.session.databases,
|
|
1234
|
+
result: null,
|
|
1235
|
+
infrastructureError: error,
|
|
1236
|
+
duration: Date.now() - startTime
|
|
1237
|
+
});
|
|
962
1238
|
devRequestEvents.emitScenarioComplete({
|
|
963
1239
|
id: scenario.id,
|
|
964
1240
|
success: false,
|
|
@@ -979,7 +1255,7 @@ var DevRunner = class {
|
|
|
979
1255
|
);
|
|
980
1256
|
if (this.hadConnectionWarning) {
|
|
981
1257
|
this.hadConnectionWarning = false;
|
|
982
|
-
log.info("
|
|
1258
|
+
log.info("Connection to platform restored");
|
|
983
1259
|
devRequestEvents.emitConnectionRestored();
|
|
984
1260
|
}
|
|
985
1261
|
if (request) {
|
|
@@ -988,31 +1264,31 @@ var DevRunner = class {
|
|
|
988
1264
|
this.backoffMs = 1e3;
|
|
989
1265
|
} catch (error) {
|
|
990
1266
|
if (error instanceof DevPollError && error.statusCode === 404) {
|
|
991
|
-
log.error("
|
|
1267
|
+
log.error("Dev session expired", { statusCode: 404 });
|
|
992
1268
|
devRequestEvents.emitSessionExpired();
|
|
993
1269
|
this.isRunning = false;
|
|
994
1270
|
return;
|
|
995
1271
|
}
|
|
996
1272
|
if ((error instanceof DevPollError || error instanceof ApiError) && error.statusCode === 401) {
|
|
997
|
-
log.warn("
|
|
1273
|
+
log.warn("Session token expired, re-authenticating");
|
|
998
1274
|
const refreshed = await this.refreshAuth();
|
|
999
1275
|
if (refreshed) {
|
|
1000
1276
|
this.backoffMs = 1e3;
|
|
1001
1277
|
continue;
|
|
1002
1278
|
}
|
|
1003
|
-
log.error("
|
|
1279
|
+
log.error("Re-authentication failed");
|
|
1004
1280
|
devRequestEvents.emitSessionExpired();
|
|
1005
1281
|
this.isRunning = false;
|
|
1006
1282
|
return;
|
|
1007
1283
|
}
|
|
1008
1284
|
if (!this.hadConnectionWarning) {
|
|
1009
1285
|
this.hadConnectionWarning = true;
|
|
1010
|
-
log.warn("
|
|
1286
|
+
log.warn("Lost connection to platform, retrying");
|
|
1011
1287
|
devRequestEvents.emitConnectionWarning(
|
|
1012
1288
|
"Lost connection to platform, retrying..."
|
|
1013
1289
|
);
|
|
1014
1290
|
}
|
|
1015
|
-
log.debug("
|
|
1291
|
+
log.debug("Backing off", { ms: this.backoffMs });
|
|
1016
1292
|
await this.sleep(this.backoffMs);
|
|
1017
1293
|
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
1018
1294
|
}
|
|
@@ -1026,9 +1302,9 @@ var DevRunner = class {
|
|
|
1026
1302
|
method: request.methodExport,
|
|
1027
1303
|
timestamp: startTime
|
|
1028
1304
|
});
|
|
1029
|
-
log.info("
|
|
1305
|
+
log.info("Method received", { requestId: request.requestId, method: request.methodExport });
|
|
1030
1306
|
try {
|
|
1031
|
-
log.debug("
|
|
1307
|
+
log.debug("Transpiling method", { path: request.methodPath });
|
|
1032
1308
|
const transpiledPath = await this.transpiler.transpile(request.methodPath);
|
|
1033
1309
|
const auth = request.roleOverride ? {
|
|
1034
1310
|
userId: this.session.auth.userId,
|
|
@@ -1063,7 +1339,28 @@ var DevRunner = class {
|
|
|
1063
1339
|
devResult
|
|
1064
1340
|
);
|
|
1065
1341
|
const duration = Date.now() - startTime;
|
|
1066
|
-
|
|
1342
|
+
if (result.success) {
|
|
1343
|
+
log.info("Method complete", { requestId: request.requestId, method: request.methodExport, duration });
|
|
1344
|
+
} else {
|
|
1345
|
+
log.warn("Method failed", {
|
|
1346
|
+
requestId: request.requestId,
|
|
1347
|
+
method: request.methodExport,
|
|
1348
|
+
duration,
|
|
1349
|
+
error: result.error ? formatErrorForDisplay(result.error) : void 0
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
logMethodExecution({
|
|
1353
|
+
requestId: request.requestId,
|
|
1354
|
+
sessionId: this.session.sessionId,
|
|
1355
|
+
methodExport: request.methodExport,
|
|
1356
|
+
methodPath: request.methodPath,
|
|
1357
|
+
input: request.input,
|
|
1358
|
+
roleOverride: request.roleOverride,
|
|
1359
|
+
authorizationToken: request.authorizationToken,
|
|
1360
|
+
databases: this.session.databases,
|
|
1361
|
+
result,
|
|
1362
|
+
duration
|
|
1363
|
+
});
|
|
1067
1364
|
devRequestEvents.emitComplete({
|
|
1068
1365
|
id: request.requestId,
|
|
1069
1366
|
success: result.success,
|
|
@@ -1073,7 +1370,7 @@ var DevRunner = class {
|
|
|
1073
1370
|
} catch (error) {
|
|
1074
1371
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1075
1372
|
const duration = Date.now() - startTime;
|
|
1076
|
-
log.error("
|
|
1373
|
+
log.error("Method error", { requestId: request.requestId, method: request.methodExport, duration, error: message });
|
|
1077
1374
|
try {
|
|
1078
1375
|
await submitDevResult(
|
|
1079
1376
|
this.appId,
|
|
@@ -1086,8 +1383,20 @@ var DevRunner = class {
|
|
|
1086
1383
|
}
|
|
1087
1384
|
);
|
|
1088
1385
|
} catch (submitErr) {
|
|
1089
|
-
log.error("
|
|
1386
|
+
log.error("Failed to report method error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
|
|
1090
1387
|
}
|
|
1388
|
+
logMethodExecution({
|
|
1389
|
+
requestId: request.requestId,
|
|
1390
|
+
sessionId: this.session.sessionId,
|
|
1391
|
+
methodExport: request.methodExport,
|
|
1392
|
+
methodPath: request.methodPath,
|
|
1393
|
+
input: request.input,
|
|
1394
|
+
roleOverride: request.roleOverride,
|
|
1395
|
+
authorizationToken: request.authorizationToken,
|
|
1396
|
+
databases: this.session.databases,
|
|
1397
|
+
result: { success: false, error: { message } },
|
|
1398
|
+
duration: Date.now() - startTime
|
|
1399
|
+
});
|
|
1091
1400
|
devRequestEvents.emitComplete({
|
|
1092
1401
|
id: request.requestId,
|
|
1093
1402
|
success: false,
|
|
@@ -1105,14 +1414,14 @@ var DevRunner = class {
|
|
|
1105
1414
|
const POLL_INTERVAL = 2e3;
|
|
1106
1415
|
const MAX_ATTEMPTS = 30;
|
|
1107
1416
|
try {
|
|
1108
|
-
log.info("
|
|
1417
|
+
log.info("Session token expired, requesting re-authentication");
|
|
1109
1418
|
const { url, token } = await requestDeviceAuth();
|
|
1110
1419
|
devRequestEvents.emitAuthRefreshStart(url);
|
|
1111
1420
|
try {
|
|
1112
1421
|
const open = (await import("open")).default;
|
|
1113
1422
|
await open(url);
|
|
1114
1423
|
} catch {
|
|
1115
|
-
log.warn("
|
|
1424
|
+
log.warn("Could not open browser \u2014 visit URL to re-authenticate");
|
|
1116
1425
|
}
|
|
1117
1426
|
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
1118
1427
|
await this.sleep(POLL_INTERVAL);
|
|
@@ -1123,7 +1432,7 @@ var DevRunner = class {
|
|
|
1123
1432
|
if (result.userId) {
|
|
1124
1433
|
setUserId(result.userId);
|
|
1125
1434
|
}
|
|
1126
|
-
log.info("
|
|
1435
|
+
log.info("Re-authentication successful");
|
|
1127
1436
|
devRequestEvents.emitAuthRefreshSuccess();
|
|
1128
1437
|
return true;
|
|
1129
1438
|
}
|
|
@@ -1131,11 +1440,11 @@ var DevRunner = class {
|
|
|
1131
1440
|
break;
|
|
1132
1441
|
}
|
|
1133
1442
|
}
|
|
1134
|
-
log.error("
|
|
1443
|
+
log.error("Re-authentication timed out or was denied");
|
|
1135
1444
|
devRequestEvents.emitAuthRefreshFailed();
|
|
1136
1445
|
return false;
|
|
1137
1446
|
} catch (err) {
|
|
1138
|
-
log.error("
|
|
1447
|
+
log.error("Re-authentication failed", { error: err instanceof Error ? err.message : String(err) });
|
|
1139
1448
|
devRequestEvents.emitAuthRefreshFailed();
|
|
1140
1449
|
return false;
|
|
1141
1450
|
}
|
|
@@ -1144,42 +1453,72 @@ var DevRunner = class {
|
|
|
1144
1453
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1145
1454
|
}
|
|
1146
1455
|
};
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
parts.push(`Response: ${String(error.response).slice(0, 200)}`);
|
|
1160
|
-
}
|
|
1161
|
-
if (error.cause && typeof error.cause === "object") {
|
|
1162
|
-
const cause = error.cause;
|
|
1163
|
-
if (cause.message) {
|
|
1164
|
-
parts.push(`Caused by: ${cause.message}`);
|
|
1165
|
-
}
|
|
1456
|
+
|
|
1457
|
+
// src/dev/browser-log.ts
|
|
1458
|
+
var ndjsonLog2 = new NdjsonLog("browser.ndjson");
|
|
1459
|
+
function initBrowserLog(projectRoot) {
|
|
1460
|
+
ndjsonLog2.init(projectRoot);
|
|
1461
|
+
}
|
|
1462
|
+
function appendBrowserLogEntries(entries) {
|
|
1463
|
+
for (const entry of entries) {
|
|
1464
|
+
ndjsonLog2.append({
|
|
1465
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1466
|
+
...entry
|
|
1467
|
+
});
|
|
1166
1468
|
}
|
|
1167
|
-
|
|
1469
|
+
}
|
|
1470
|
+
function closeBrowserLog() {
|
|
1471
|
+
ndjsonLog2.close();
|
|
1168
1472
|
}
|
|
1169
1473
|
|
|
1170
1474
|
// src/dev/proxy.ts
|
|
1171
1475
|
import http from "http";
|
|
1476
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1172
1477
|
var DevProxy = class {
|
|
1173
|
-
constructor(upstreamPort, clientContext, bindAddress = "127.0.0.1") {
|
|
1478
|
+
constructor(upstreamPort, clientContext, bindAddress = "127.0.0.1", browserAgentUrl) {
|
|
1174
1479
|
this.upstreamPort = upstreamPort;
|
|
1175
1480
|
this.clientContext = clientContext;
|
|
1176
1481
|
this.bindAddress = bindAddress;
|
|
1482
|
+
this.browserAgentUrl = browserAgentUrl;
|
|
1177
1483
|
}
|
|
1178
1484
|
server = null;
|
|
1179
1485
|
proxyPort = null;
|
|
1486
|
+
commandQueue = [];
|
|
1487
|
+
pendingResults = /* @__PURE__ */ new Map();
|
|
1488
|
+
lastBrowserPoll = 0;
|
|
1180
1489
|
updateClientContext(context) {
|
|
1181
1490
|
this.clientContext = context;
|
|
1182
|
-
log.info("proxy
|
|
1491
|
+
log.info("Dev proxy context updated after role change");
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Whether a browser agent is actively polling for commands.
|
|
1495
|
+
* Based on whether we've seen a poll within the last 500ms.
|
|
1496
|
+
*/
|
|
1497
|
+
isBrowserConnected() {
|
|
1498
|
+
return Date.now() - this.lastBrowserPoll < 500;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Dispatch a browser command and wait for the result.
|
|
1502
|
+
* The command is queued for the browser agent to pick up via polling.
|
|
1503
|
+
* Returns a promise that resolves when the browser posts the result back.
|
|
1504
|
+
*/
|
|
1505
|
+
dispatchBrowserCommand(steps, timeoutMs = 3e4) {
|
|
1506
|
+
if (!this.isBrowserConnected()) {
|
|
1507
|
+
return Promise.reject(
|
|
1508
|
+
new Error("No browser connected, please refresh the MindStudio preview")
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
const id = randomBytes3(4).toString("hex");
|
|
1512
|
+
log.info("Browser command queued", { id, stepCount: steps.length, commands: steps.map((s) => s.command) });
|
|
1513
|
+
return new Promise((resolve2, reject) => {
|
|
1514
|
+
const timeout = setTimeout(() => {
|
|
1515
|
+
this.pendingResults.delete(id);
|
|
1516
|
+
log.warn("Browser command timed out", { id, pendingCount: this.pendingResults.size, queueLength: this.commandQueue.length });
|
|
1517
|
+
reject(new Error("Browser command timed out"));
|
|
1518
|
+
}, timeoutMs);
|
|
1519
|
+
this.pendingResults.set(id, { resolve: resolve2, timeout });
|
|
1520
|
+
this.commandQueue.push({ id, steps });
|
|
1521
|
+
});
|
|
1183
1522
|
}
|
|
1184
1523
|
async start(preferredPort) {
|
|
1185
1524
|
const server = http.createServer((req, res) => {
|
|
@@ -1194,10 +1533,10 @@ var DevProxy = class {
|
|
|
1194
1533
|
const assignedPort = await this.listenOnPort(server, port);
|
|
1195
1534
|
this.server = server;
|
|
1196
1535
|
this.proxyPort = assignedPort;
|
|
1197
|
-
log.info("proxy
|
|
1536
|
+
log.info("Dev proxy started", { port: assignedPort, bind: this.bindAddress });
|
|
1198
1537
|
return assignedPort;
|
|
1199
1538
|
} catch {
|
|
1200
|
-
log.warn("
|
|
1539
|
+
log.warn("Proxy port in use, trying next", { port });
|
|
1201
1540
|
}
|
|
1202
1541
|
}
|
|
1203
1542
|
throw new Error("Failed to start proxy server");
|
|
@@ -1222,77 +1561,196 @@ var DevProxy = class {
|
|
|
1222
1561
|
}
|
|
1223
1562
|
stop() {
|
|
1224
1563
|
if (this.server) {
|
|
1225
|
-
log.info("proxy
|
|
1564
|
+
log.info("Dev proxy stopping");
|
|
1226
1565
|
this.server.close();
|
|
1227
1566
|
this.server = null;
|
|
1228
1567
|
this.proxyPort = null;
|
|
1229
1568
|
}
|
|
1569
|
+
for (const [id, pending2] of this.pendingResults) {
|
|
1570
|
+
clearTimeout(pending2.timeout);
|
|
1571
|
+
pending2.resolve({ id, steps: [], error: "Proxy stopped" });
|
|
1572
|
+
}
|
|
1573
|
+
this.pendingResults.clear();
|
|
1574
|
+
this.commandQueue.length = 0;
|
|
1230
1575
|
}
|
|
1231
1576
|
getPort() {
|
|
1232
1577
|
return this.proxyPort;
|
|
1233
1578
|
}
|
|
1579
|
+
// ---------------------------------------------------------------------------
|
|
1580
|
+
// CORS helper
|
|
1581
|
+
// ---------------------------------------------------------------------------
|
|
1582
|
+
corsHeaders(req) {
|
|
1583
|
+
const origin = req.headers.origin;
|
|
1584
|
+
if (!origin) return {};
|
|
1585
|
+
return {
|
|
1586
|
+
"access-control-allow-origin": origin,
|
|
1587
|
+
"access-control-allow-private-network": "true"
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
// ---------------------------------------------------------------------------
|
|
1591
|
+
// Request routing
|
|
1592
|
+
// ---------------------------------------------------------------------------
|
|
1234
1593
|
handleRequest(clientReq, clientRes) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1594
|
+
if (clientReq.url?.startsWith("/__mindstudio_dev__/")) {
|
|
1595
|
+
if (clientReq.url === "/__mindstudio_dev__/logs" && clientReq.method === "POST") {
|
|
1596
|
+
this.handleBrowserLogs(clientReq, clientRes);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
if (clientReq.url === "/__mindstudio_dev__/commands" && clientReq.method === "GET") {
|
|
1600
|
+
this.handleGetCommand(clientReq, clientRes);
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (clientReq.url === "/__mindstudio_dev__/results" && clientReq.method === "POST") {
|
|
1604
|
+
this.handlePostResult(clientReq, clientRes);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (clientReq.method === "OPTIONS" && clientReq.headers.origin) {
|
|
1237
1609
|
clientRes.writeHead(204, {
|
|
1238
|
-
|
|
1239
|
-
"
|
|
1240
|
-
"
|
|
1241
|
-
"Access-Control-Allow-Headers": "*"
|
|
1610
|
+
...this.corsHeaders(clientReq),
|
|
1611
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
1612
|
+
"access-control-allow-headers": "*"
|
|
1242
1613
|
});
|
|
1243
1614
|
clientRes.end();
|
|
1244
1615
|
return;
|
|
1245
1616
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const upstreamReq = http.request(
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1617
|
+
this.forwardToUpstream(clientReq, clientRes);
|
|
1618
|
+
}
|
|
1619
|
+
// ---------------------------------------------------------------------------
|
|
1620
|
+
// Upstream forwarding
|
|
1621
|
+
// ---------------------------------------------------------------------------
|
|
1622
|
+
forwardToUpstream(clientReq, clientRes) {
|
|
1623
|
+
const cors = this.corsHeaders(clientReq);
|
|
1624
|
+
const upstreamReq = http.request(
|
|
1625
|
+
{
|
|
1626
|
+
hostname: "127.0.0.1",
|
|
1627
|
+
port: this.upstreamPort,
|
|
1628
|
+
path: clientReq.url,
|
|
1629
|
+
method: clientReq.method,
|
|
1630
|
+
headers: { ...clientReq.headers, host: `localhost:${this.upstreamPort}` }
|
|
1631
|
+
},
|
|
1632
|
+
(upstreamRes) => {
|
|
1633
|
+
const contentType = upstreamRes.headers["content-type"] ?? "";
|
|
1634
|
+
const isHtml = contentType.startsWith("text/html");
|
|
1635
|
+
if (isHtml) {
|
|
1636
|
+
const chunks = [];
|
|
1637
|
+
upstreamRes.on("data", (chunk) => chunks.push(chunk));
|
|
1638
|
+
upstreamRes.on("end", () => {
|
|
1639
|
+
let html = Buffer.concat(chunks).toString("utf-8");
|
|
1640
|
+
html = this.injectScripts(html);
|
|
1641
|
+
const headers = {
|
|
1642
|
+
...upstreamRes.headers,
|
|
1643
|
+
...cors,
|
|
1644
|
+
"content-length": String(Buffer.byteLength(html, "utf-8")),
|
|
1645
|
+
"cache-control": "no-store, no-cache, must-revalidate"
|
|
1646
|
+
};
|
|
1647
|
+
delete headers["content-encoding"];
|
|
1648
|
+
delete headers["etag"];
|
|
1649
|
+
log.debug("Dev proxy injected context into HTML", { path: clientReq.url, size: html.length });
|
|
1650
|
+
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1651
|
+
clientRes.end(html);
|
|
1652
|
+
});
|
|
1653
|
+
} else {
|
|
1654
|
+
const headers = {
|
|
1655
|
+
...upstreamRes.headers,
|
|
1656
|
+
...cors,
|
|
1657
|
+
"cache-control": "no-store, no-cache, must-revalidate"
|
|
1658
|
+
};
|
|
1266
1659
|
delete headers["etag"];
|
|
1267
|
-
if (origin) {
|
|
1268
|
-
headers["access-control-allow-origin"] = origin;
|
|
1269
|
-
headers["access-control-allow-private-network"] = "true";
|
|
1270
|
-
}
|
|
1271
|
-
log.debug("proxy HTML injected", { path: clientReq.url, size: html.length });
|
|
1272
1660
|
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1273
|
-
|
|
1274
|
-
});
|
|
1275
|
-
} else {
|
|
1276
|
-
const headers = { ...upstreamRes.headers };
|
|
1277
|
-
headers["cache-control"] = "no-store, no-cache, must-revalidate";
|
|
1278
|
-
delete headers["etag"];
|
|
1279
|
-
if (origin) {
|
|
1280
|
-
headers["access-control-allow-origin"] = origin;
|
|
1281
|
-
headers["access-control-allow-private-network"] = "true";
|
|
1661
|
+
upstreamRes.pipe(clientRes);
|
|
1282
1662
|
}
|
|
1283
|
-
clientRes.writeHead(upstreamRes.statusCode ?? 200, headers);
|
|
1284
|
-
upstreamRes.pipe(clientRes);
|
|
1285
1663
|
}
|
|
1286
|
-
|
|
1664
|
+
);
|
|
1287
1665
|
upstreamReq.on("error", (err) => {
|
|
1288
|
-
log.warn("proxy
|
|
1666
|
+
log.warn("Dev proxy cannot reach dev server", { path: clientReq.url, error: err.message });
|
|
1289
1667
|
clientRes.writeHead(502);
|
|
1290
1668
|
clientRes.end(`Proxy error: ${err.message}`);
|
|
1291
1669
|
});
|
|
1292
1670
|
clientReq.pipe(upstreamReq);
|
|
1293
1671
|
}
|
|
1672
|
+
// ---------------------------------------------------------------------------
|
|
1673
|
+
// Browser agent endpoints
|
|
1674
|
+
// ---------------------------------------------------------------------------
|
|
1675
|
+
handleBrowserLogs(clientReq, clientRes) {
|
|
1676
|
+
const chunks = [];
|
|
1677
|
+
clientReq.on("data", (chunk) => chunks.push(chunk));
|
|
1678
|
+
clientReq.on("end", () => {
|
|
1679
|
+
try {
|
|
1680
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
1681
|
+
const entries = JSON.parse(body);
|
|
1682
|
+
if (Array.isArray(entries)) {
|
|
1683
|
+
appendBrowserLogEntries(entries);
|
|
1684
|
+
}
|
|
1685
|
+
} catch {
|
|
1686
|
+
}
|
|
1687
|
+
clientRes.writeHead(204, this.corsHeaders(clientReq));
|
|
1688
|
+
clientRes.end();
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
handleGetCommand(clientReq, clientRes) {
|
|
1692
|
+
this.lastBrowserPoll = Date.now();
|
|
1693
|
+
const command = this.commandQueue.shift();
|
|
1694
|
+
if (command) {
|
|
1695
|
+
log.info("Browser command dispatched to agent", { id: command.id, commands: command.steps.map((s) => s.command) });
|
|
1696
|
+
clientRes.writeHead(200, {
|
|
1697
|
+
...this.corsHeaders(clientReq),
|
|
1698
|
+
"content-type": "application/json",
|
|
1699
|
+
"cache-control": "no-store"
|
|
1700
|
+
});
|
|
1701
|
+
clientRes.end(JSON.stringify(command));
|
|
1702
|
+
} else {
|
|
1703
|
+
clientRes.writeHead(204, {
|
|
1704
|
+
...this.corsHeaders(clientReq),
|
|
1705
|
+
"cache-control": "no-store"
|
|
1706
|
+
});
|
|
1707
|
+
clientRes.end();
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
handlePostResult(clientReq, clientRes) {
|
|
1711
|
+
const chunks = [];
|
|
1712
|
+
clientReq.on("data", (chunk) => chunks.push(chunk));
|
|
1713
|
+
clientReq.on("end", () => {
|
|
1714
|
+
try {
|
|
1715
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
1716
|
+
const result = JSON.parse(body);
|
|
1717
|
+
if (result?.id) {
|
|
1718
|
+
const pending2 = this.pendingResults.get(result.id);
|
|
1719
|
+
if (pending2) {
|
|
1720
|
+
log.info("Browser command result received", { id: result.id, stepCount: result.steps?.length, duration: result.duration });
|
|
1721
|
+
clearTimeout(pending2.timeout);
|
|
1722
|
+
this.pendingResults.delete(result.id);
|
|
1723
|
+
pending2.resolve(result);
|
|
1724
|
+
} else {
|
|
1725
|
+
log.warn("Browser command result received but no pending command found", { id: result.id, pendingIds: [...this.pendingResults.keys()] });
|
|
1726
|
+
}
|
|
1727
|
+
} else {
|
|
1728
|
+
log.warn("Browser command result received with no id", { bodyLength: body.length });
|
|
1729
|
+
}
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
log.warn("Browser command result parse error", { error: err instanceof Error ? err.message : String(err) });
|
|
1732
|
+
}
|
|
1733
|
+
clientRes.writeHead(204, this.corsHeaders(clientReq));
|
|
1734
|
+
clientRes.end();
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Inject window.__MINDSTUDIO__ context and browser agent script tag into HTML.
|
|
1739
|
+
*/
|
|
1740
|
+
injectScripts(html) {
|
|
1741
|
+
const contextScript = `<script>window.__MINDSTUDIO__=${JSON.stringify(this.clientContext)};</script>`;
|
|
1742
|
+
const agentUrl = this.browserAgentUrl || "https://unpkg.com/@mindstudio-ai/browser-agent/dist/index.js";
|
|
1743
|
+
const agentScript = `<script async src="${agentUrl}"></script>`;
|
|
1744
|
+
const injection = `${contextScript}
|
|
1745
|
+
${agentScript}`;
|
|
1746
|
+
if (html.includes("</head>")) {
|
|
1747
|
+
return html.replace("</head>", `${injection}
|
|
1748
|
+
</head>`);
|
|
1749
|
+
}
|
|
1750
|
+
return injection + "\n" + html;
|
|
1751
|
+
}
|
|
1294
1752
|
handleUpgrade(clientReq, clientSocket, head) {
|
|
1295
|
-
log.debug("proxy WebSocket upgrade", { path: clientReq.url });
|
|
1753
|
+
log.debug("Dev proxy WebSocket upgrade", { path: clientReq.url });
|
|
1296
1754
|
const options = {
|
|
1297
1755
|
hostname: "127.0.0.1",
|
|
1298
1756
|
port: this.upstreamPort,
|
|
@@ -1329,22 +1787,14 @@ var DevProxy = class {
|
|
|
1329
1787
|
upstreamReq.end();
|
|
1330
1788
|
}
|
|
1331
1789
|
};
|
|
1332
|
-
function injectClientContext(html, context) {
|
|
1333
|
-
const script = `<script>window.__MINDSTUDIO__=${JSON.stringify(context)};</script>`;
|
|
1334
|
-
if (html.includes("</head>")) {
|
|
1335
|
-
return html.replace("</head>", `${script}
|
|
1336
|
-
</head>`);
|
|
1337
|
-
}
|
|
1338
|
-
return script + "\n" + html;
|
|
1339
|
-
}
|
|
1340
1790
|
|
|
1341
1791
|
// src/dev/app-config.ts
|
|
1342
1792
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
1343
|
-
import { join as
|
|
1793
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1344
1794
|
function detectAppConfig(cwd = process.cwd()) {
|
|
1345
|
-
const appJsonPath =
|
|
1795
|
+
const appJsonPath = join4(cwd, "mindstudio.json");
|
|
1346
1796
|
if (!existsSync2(appJsonPath)) {
|
|
1347
|
-
log.debug("
|
|
1797
|
+
log.debug("mindstudio.json not found", { path: appJsonPath });
|
|
1348
1798
|
return null;
|
|
1349
1799
|
}
|
|
1350
1800
|
try {
|
|
@@ -1363,7 +1813,7 @@ function detectAppConfig(cwd = process.cwd()) {
|
|
|
1363
1813
|
scenarios: parsed.scenarios ?? [],
|
|
1364
1814
|
interfaces: parsed.interfaces ?? []
|
|
1365
1815
|
};
|
|
1366
|
-
log.
|
|
1816
|
+
log.info("Loaded mindstudio.json", {
|
|
1367
1817
|
appId: config2.appId,
|
|
1368
1818
|
roles: config2.roles.length,
|
|
1369
1819
|
methods: config2.methods.length,
|
|
@@ -1373,7 +1823,7 @@ function detectAppConfig(cwd = process.cwd()) {
|
|
|
1373
1823
|
});
|
|
1374
1824
|
return config2;
|
|
1375
1825
|
} catch (err) {
|
|
1376
|
-
log.warn("
|
|
1826
|
+
log.warn("Failed to parse mindstudio.json", { error: err instanceof Error ? err.message : String(err) });
|
|
1377
1827
|
return null;
|
|
1378
1828
|
}
|
|
1379
1829
|
}
|
|
@@ -1384,7 +1834,7 @@ function getWebInterfaceConfig(appConfig, cwd = process.cwd()) {
|
|
|
1384
1834
|
if (!webInterface) {
|
|
1385
1835
|
return null;
|
|
1386
1836
|
}
|
|
1387
|
-
const configPath =
|
|
1837
|
+
const configPath = join4(cwd, webInterface.path);
|
|
1388
1838
|
if (!existsSync2(configPath)) {
|
|
1389
1839
|
return null;
|
|
1390
1840
|
}
|
|
@@ -1410,22 +1860,27 @@ function getWebProjectDir(appConfig, cwd = process.cwd()) {
|
|
|
1410
1860
|
if (!webInterface) {
|
|
1411
1861
|
return null;
|
|
1412
1862
|
}
|
|
1413
|
-
return dirname2(
|
|
1863
|
+
return dirname2(join4(cwd, webInterface.path));
|
|
1414
1864
|
}
|
|
1415
1865
|
function readTableSources(appConfig, cwd = process.cwd()) {
|
|
1416
1866
|
const results = [];
|
|
1417
1867
|
for (const table of appConfig.tables) {
|
|
1418
|
-
const filePath =
|
|
1868
|
+
const filePath = join4(cwd, table.path);
|
|
1419
1869
|
if (!existsSync2(filePath)) {
|
|
1870
|
+
log.warn("Table source file not found", { table: table.export, path: table.path });
|
|
1420
1871
|
continue;
|
|
1421
1872
|
}
|
|
1422
1873
|
try {
|
|
1423
1874
|
const source = readFileSync(filePath, "utf-8");
|
|
1424
1875
|
const name = table.export;
|
|
1425
1876
|
results.push({ name, source });
|
|
1426
|
-
} catch {
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
log.warn("Table source file unreadable", { table: table.export, path: table.path, error: err instanceof Error ? err.message : String(err) });
|
|
1427
1879
|
}
|
|
1428
1880
|
}
|
|
1881
|
+
if (results.length < appConfig.tables.length) {
|
|
1882
|
+
log.warn("Missing " + (appConfig.tables.length - results.length) + " table source file(s)", { found: results.length, expected: appConfig.tables.length });
|
|
1883
|
+
}
|
|
1429
1884
|
return results;
|
|
1430
1885
|
}
|
|
1431
1886
|
function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
@@ -1434,9 +1889,9 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
1434
1889
|
const firstMethodPath = appConfig.methods[0].path;
|
|
1435
1890
|
const parts = firstMethodPath.split("/");
|
|
1436
1891
|
for (let i = parts.length - 1; i >= 1; i--) {
|
|
1437
|
-
const candidate =
|
|
1438
|
-
if (existsSync2(
|
|
1439
|
-
if (!existsSync2(
|
|
1892
|
+
const candidate = join4(cwd, ...parts.slice(0, i));
|
|
1893
|
+
if (existsSync2(join4(candidate, "package.json"))) {
|
|
1894
|
+
if (!existsSync2(join4(candidate, "node_modules"))) {
|
|
1440
1895
|
dirs.push(candidate);
|
|
1441
1896
|
}
|
|
1442
1897
|
break;
|
|
@@ -1444,8 +1899,8 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
|
|
|
1444
1899
|
}
|
|
1445
1900
|
}
|
|
1446
1901
|
const webProjectDir = getWebProjectDir(appConfig, cwd);
|
|
1447
|
-
if (webProjectDir && existsSync2(
|
|
1448
|
-
if (!existsSync2(
|
|
1902
|
+
if (webProjectDir && existsSync2(join4(webProjectDir, "package.json"))) {
|
|
1903
|
+
if (!existsSync2(join4(webProjectDir, "node_modules"))) {
|
|
1449
1904
|
dirs.push(webProjectDir);
|
|
1450
1905
|
}
|
|
1451
1906
|
}
|
|
@@ -1474,11 +1929,11 @@ function detectGitBranch() {
|
|
|
1474
1929
|
|
|
1475
1930
|
// src/dev/table-watcher.ts
|
|
1476
1931
|
import { watch } from "chokidar";
|
|
1477
|
-
import { join as
|
|
1932
|
+
import { join as join5, dirname as dirname3, basename as basename2 } from "path";
|
|
1478
1933
|
function watchTableFiles(tables, cwd, onChanged) {
|
|
1479
1934
|
if (tables.length === 0) return () => {
|
|
1480
1935
|
};
|
|
1481
|
-
const filePaths = tables.map((t) =>
|
|
1936
|
+
const filePaths = tables.map((t) => join5(cwd, t.path));
|
|
1482
1937
|
let syncTimer;
|
|
1483
1938
|
const watcher = watch(filePaths, {
|
|
1484
1939
|
ignoreInitial: true,
|
|
@@ -1491,13 +1946,13 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
1491
1946
|
});
|
|
1492
1947
|
const dirToFiles = /* @__PURE__ */ new Map();
|
|
1493
1948
|
for (const table of tables) {
|
|
1494
|
-
const absPath =
|
|
1949
|
+
const absPath = join5(cwd, table.path);
|
|
1495
1950
|
const dir = dirname3(absPath);
|
|
1496
1951
|
const file = basename2(absPath);
|
|
1497
1952
|
if (!dirToFiles.has(dir)) dirToFiles.set(dir, /* @__PURE__ */ new Set());
|
|
1498
1953
|
dirToFiles.get(dir).add(file);
|
|
1499
1954
|
}
|
|
1500
|
-
log.info("table
|
|
1955
|
+
log.info("Watching table source files", {
|
|
1501
1956
|
dirs: dirToFiles.size,
|
|
1502
1957
|
tables: tables.length
|
|
1503
1958
|
});
|
|
@@ -1509,9 +1964,9 @@ function watchTableFiles(tables, cwd, onChanged) {
|
|
|
1509
1964
|
|
|
1510
1965
|
// src/dev/config-watcher.ts
|
|
1511
1966
|
import { watch as watch2 } from "chokidar";
|
|
1512
|
-
import { join as
|
|
1967
|
+
import { join as join6 } from "path";
|
|
1513
1968
|
function watchConfigFile(cwd, onChanged) {
|
|
1514
|
-
const configPath =
|
|
1969
|
+
const configPath = join6(cwd, "mindstudio.json");
|
|
1515
1970
|
let debounceTimer;
|
|
1516
1971
|
const watcher = watch2(configPath, {
|
|
1517
1972
|
ignoreInitial: true,
|
|
@@ -1520,11 +1975,10 @@ function watchConfigFile(cwd, onChanged) {
|
|
|
1520
1975
|
watcher.on("all", () => {
|
|
1521
1976
|
clearTimeout(debounceTimer);
|
|
1522
1977
|
debounceTimer = setTimeout(() => {
|
|
1523
|
-
log.info("config-watcher mindstudio.json changed");
|
|
1524
1978
|
onChanged();
|
|
1525
1979
|
}, 500);
|
|
1526
1980
|
});
|
|
1527
|
-
log.info("
|
|
1981
|
+
log.info("Watching mindstudio.json for changes", { path: configPath });
|
|
1528
1982
|
return () => {
|
|
1529
1983
|
clearTimeout(debounceTimer);
|
|
1530
1984
|
watcher.close();
|
|
@@ -1551,6 +2005,7 @@ export {
|
|
|
1551
2005
|
initLoggerHeadless,
|
|
1552
2006
|
initLoggerInteractive,
|
|
1553
2007
|
syncSchema,
|
|
2008
|
+
fetchCallbackToken,
|
|
1554
2009
|
devRequestEvents,
|
|
1555
2010
|
pollForRequest,
|
|
1556
2011
|
submitProgress,
|
|
@@ -1562,7 +2017,11 @@ export {
|
|
|
1562
2017
|
pollDeviceAuth,
|
|
1563
2018
|
getEditorSessions,
|
|
1564
2019
|
disconnectHeartbeat,
|
|
2020
|
+
initRequestLog,
|
|
2021
|
+
closeRequestLog,
|
|
1565
2022
|
DevRunner,
|
|
2023
|
+
initBrowserLog,
|
|
2024
|
+
closeBrowserLog,
|
|
1566
2025
|
DevProxy,
|
|
1567
2026
|
detectAppConfig,
|
|
1568
2027
|
getWebInterfaceConfig,
|
|
@@ -1574,4 +2033,4 @@ export {
|
|
|
1574
2033
|
watchTableFiles,
|
|
1575
2034
|
watchConfigFile
|
|
1576
2035
|
};
|
|
1577
|
-
//# sourceMappingURL=chunk-
|
|
2036
|
+
//# sourceMappingURL=chunk-XP4GPID6.js.map
|