@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.
@@ -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("transpiler Transpiling", { methodPath });
522
+ log.debug("Transpiling method", { methodPath });
382
523
  const nodeModulesDir = findNearestNodeModules(dirname(absolutePath));
383
524
  if (!nodeModulesDir) {
384
- log.error("transpiler No node_modules found", { methodPath, searchStart: dirname(absolutePath) });
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("transpiler Found node_modules", { path: nodeModulesDir });
390
- const outDir = join(nodeModulesDir, ".cache", "mindstudio-dev");
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 = join(outDir, `${name}.__ms_dev__.mjs`);
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(`transpiler Transpiled in ${Date.now() - start}ms`, { methodPath, outfile });
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("transpiler Cleaning up", { fileCount: this.outputFiles.size });
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 = join(dir, entry.name);
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("transpiler Removing orphaned file", { path: fullPath });
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 = join(dir, "node_modules");
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 join2 } from "path";
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 = join2(
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("executor Spawning persistent worker", { cwd: projectRoot, scriptPath });
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("executor Worker exited", { code });
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.debug("executor Worker stderr", { text: text.slice(0, 500) });
744
+ if (text) log.warn("Method process stderr", { text: text.slice(0, 500) });
604
745
  });
605
746
  worker = child;
606
- log.info("executor Persistent worker ready", { pid: child.pid });
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("executor Sending to worker", { id, methodExport: opts.methodExport });
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("executor Timeout after 30s", { id, methodExport: opts.methodExport });
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("runner Starting session", { appId: this.appId, branch: this.startOpts.branch });
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("runner Session started", { sessionId: session.sessionId, branch: session.branch });
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("runner Stopping session");
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("runner Failed to stop session cleanly", { error: err instanceof Error ? err.message : String(err) });
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("runner Impersonating", { roles });
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("runner Clearing impersonation");
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("runner Failed to refresh client context", { error: err instanceof Error ? err.message : String(err) });
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("runner Running scenario", { id: scenario.id, name: scenarioName });
1168
+ log.info("Scenario starting", { id: scenario.id, name: scenarioName });
914
1169
  try {
915
- log.debug("runner Truncating database for scenario");
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.debug("runner Transpiling scenario", { path: scenario.path });
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.debug("runner Executing scenario seed", { export: scenario.export });
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("runner Scenario seed failed", { id: scenario.id, 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.debug("runner Impersonating for scenario", { roles: scenario.roles });
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("runner Scenario complete", { id: scenario.id, duration, roles: scenario.roles });
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("runner Scenario failed", { id: scenario.id, 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("runner Connection restored");
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("runner Session expired (404)");
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("runner Auth token expired (401), attempting refresh");
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("runner Auth refresh failed, stopping");
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("runner Connection lost, retrying...");
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("runner Backing off", { ms: this.backoffMs });
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("runner Request received", { requestId: request.requestId, method: request.methodExport });
1305
+ log.info("Method received", { requestId: request.requestId, method: request.methodExport });
1030
1306
  try {
1031
- log.debug("runner Transpiling", { path: request.methodPath });
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
- log.info("runner Request complete", { requestId: request.requestId, success: result.success, duration });
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("runner Request failed", { requestId: request.requestId, duration, error: message });
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("runner Failed to submit error result", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
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("runner Auth expired, requesting re-authentication");
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("runner Could not open browser for auth \u2014 user must visit URL manually");
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("runner Auth refreshed successfully");
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("runner Auth refresh timed out or was denied");
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("runner Auth refresh failed", { error: err instanceof Error ? err.message : String(err) });
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
- function formatErrorForDisplay(error) {
1148
- const parts = [];
1149
- if (error.message) {
1150
- parts.push(String(error.message));
1151
- }
1152
- const code = error.code ?? error.statusCode ?? error.status;
1153
- if (code !== void 0) {
1154
- parts.push(`(code: ${code})`);
1155
- }
1156
- if (error.body) {
1157
- parts.push(`Response: ${String(error.body).slice(0, 200)}`);
1158
- } else if (error.response) {
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
- return parts.join("\n");
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 Client context updated");
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 Started", { port: assignedPort, bind: this.bindAddress });
1536
+ log.info("Dev proxy started", { port: assignedPort, bind: this.bindAddress });
1198
1537
  return assignedPort;
1199
1538
  } catch {
1200
- log.warn("proxy Port in use, trying next", { port });
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 Stopping");
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
- const origin = clientReq.headers.origin;
1236
- if (clientReq.method === "OPTIONS" && origin) {
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
- "Access-Control-Allow-Origin": origin,
1239
- "Access-Control-Allow-Private-Network": "true",
1240
- "Access-Control-Allow-Methods": "GET, OPTIONS",
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
- const options = {
1247
- hostname: "127.0.0.1",
1248
- port: this.upstreamPort,
1249
- path: clientReq.url,
1250
- method: clientReq.method,
1251
- headers: { ...clientReq.headers, host: `localhost:${this.upstreamPort}` }
1252
- };
1253
- const upstreamReq = http.request(options, (upstreamRes) => {
1254
- const contentType = upstreamRes.headers["content-type"] ?? "";
1255
- const isHtml = contentType.startsWith("text/html");
1256
- if (isHtml) {
1257
- const chunks = [];
1258
- upstreamRes.on("data", (chunk) => chunks.push(chunk));
1259
- upstreamRes.on("end", () => {
1260
- let html = Buffer.concat(chunks).toString("utf-8");
1261
- html = injectClientContext(html, this.clientContext);
1262
- const headers = { ...upstreamRes.headers };
1263
- headers["content-length"] = String(Buffer.byteLength(html, "utf-8"));
1264
- headers["cache-control"] = "no-store, no-cache, must-revalidate";
1265
- delete headers["content-encoding"];
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
- clientRes.end(html);
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 Upstream error", { path: clientReq.url, error: err.message });
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 join3, dirname as dirname2 } from "path";
1793
+ import { join as join4, dirname as dirname2 } from "path";
1344
1794
  function detectAppConfig(cwd = process.cwd()) {
1345
- const appJsonPath = join3(cwd, "mindstudio.json");
1795
+ const appJsonPath = join4(cwd, "mindstudio.json");
1346
1796
  if (!existsSync2(appJsonPath)) {
1347
- log.debug("config mindstudio.json not found", { path: appJsonPath });
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.debug("config Detected mindstudio.json", {
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("config Failed to parse mindstudio.json", { error: err instanceof Error ? err.message : String(err) });
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 = join3(cwd, webInterface.path);
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(join3(cwd, webInterface.path));
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 = join3(cwd, table.path);
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 = join3(cwd, ...parts.slice(0, i));
1438
- if (existsSync2(join3(candidate, "package.json"))) {
1439
- if (!existsSync2(join3(candidate, "node_modules"))) {
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(join3(webProjectDir, "package.json"))) {
1448
- if (!existsSync2(join3(webProjectDir, "node_modules"))) {
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 join4, dirname as dirname3, basename as basename2 } from "path";
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) => join4(cwd, t.path));
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 = join4(cwd, table.path);
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-watcher Watching files", {
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 join5 } from "path";
1967
+ import { join as join6 } from "path";
1513
1968
  function watchConfigFile(cwd, onChanged) {
1514
- const configPath = join5(cwd, "mindstudio.json");
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("config-watcher Watching mindstudio.json", { path: configPath });
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-C4IDR7TD.js.map
2036
+ //# sourceMappingURL=chunk-XP4GPID6.js.map