@mindstudio-ai/local-model-tunnel 0.5.31 → 0.5.33

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.
@@ -7,7 +7,7 @@ import {
7
7
  setProviderInstallPath,
8
8
  submitProgress,
9
9
  submitResult
10
- } from "./chunk-C64JURPM.js";
10
+ } from "./chunk-U6BBTRV3.js";
11
11
 
12
12
  // src/providers/ollama/index.ts
13
13
  import { Ollama } from "ollama";
@@ -1395,4 +1395,4 @@ export {
1395
1395
  requestEvents,
1396
1396
  TunnelRunner
1397
1397
  };
1398
- //# sourceMappingURL=chunk-JSH2XCYI.js.map
1398
+ //# sourceMappingURL=chunk-53B4RMBL.js.map
@@ -6,6 +6,7 @@ import {
6
6
  detectAppConfig,
7
7
  detectGitBranch,
8
8
  devRequestEvents,
9
+ fetchCallbackToken,
9
10
  getApiBaseUrl,
10
11
  getApiKey,
11
12
  getConfigPath,
@@ -22,7 +23,7 @@ import {
22
23
  syncSchema,
23
24
  watchConfigFile,
24
25
  watchTableFiles
25
- } from "./chunk-C64JURPM.js";
26
+ } from "./chunk-U6BBTRV3.js";
26
27
 
27
28
  // src/dev/ipc/ipc.ts
28
29
  function emitEvent(event, data) {
@@ -94,7 +95,8 @@ async function handleRunScenario(ctx, cmd) {
94
95
  if (!scenario) throw new Error(`Unknown scenario: ${cmd.scenarioId}`);
95
96
  const scenarioName = scenario.name ?? scenario.export;
96
97
  ctx.started({ scenarioId: scenario.id, name: scenarioName });
97
- const result = await ctx.state.runner.runScenario(scenario);
98
+ const skipTruncate = cmd.skipTruncate === true;
99
+ const result = await ctx.state.runner.runScenario(scenario, { skipTruncate });
98
100
  return {
99
101
  success: result.success,
100
102
  scenarioId: scenario.id,
@@ -248,6 +250,44 @@ async function handleResetBrowser(ctx) {
248
250
  return {};
249
251
  }
250
252
 
253
+ // src/dev/stdin-commands/db-query.ts
254
+ async function handleDbQuery(ctx, cmd) {
255
+ if (!ctx.state.runner) throw new Error("No active session");
256
+ const session = ctx.state.runner.getSession();
257
+ if (!session) throw new Error("No active session");
258
+ const sql = cmd.sql;
259
+ if (!sql) throw new Error('db-query requires "sql"');
260
+ let databaseId = cmd.databaseId;
261
+ if (!databaseId) {
262
+ if (session.databases.length === 0) throw new Error("No databases available");
263
+ databaseId = session.databases[0].id;
264
+ }
265
+ ctx.started({ databaseId, sql });
266
+ const token = await fetchCallbackToken(ctx.state.appConfig.appId, session.sessionId);
267
+ const url = `${getApiBaseUrl()}/_internal/v2/db/query`;
268
+ const res = await fetch(url, {
269
+ method: "POST",
270
+ headers: {
271
+ "Content-Type": "application/json",
272
+ Authorization: token
273
+ },
274
+ body: JSON.stringify({
275
+ databaseId,
276
+ queries: [{ sql }]
277
+ })
278
+ });
279
+ if (!res.ok) {
280
+ const text = await res.text().catch(() => "");
281
+ throw new Error(`Database query failed: ${res.status} ${text}`);
282
+ }
283
+ const data = await res.json();
284
+ return {
285
+ success: true,
286
+ databaseId,
287
+ results: data.results
288
+ };
289
+ }
290
+
251
291
  // src/dev/stdin-commands/index.ts
252
292
  var handlers = {
253
293
  "run-method": handleRunMethod,
@@ -258,6 +298,7 @@ var handlers = {
258
298
  "screenshotFullPage": handleScreenshotFullPage,
259
299
  "browser-status": handleBrowserStatus,
260
300
  "reset-browser": handleResetBrowser,
301
+ "db-query": handleDbQuery,
261
302
  "dev-server-restarting": handleDevServerRestarting
262
303
  };
263
304
  function setupStdinCommands(state, cwd) {
@@ -500,22 +541,41 @@ async function startHeadless(opts = {}) {
500
541
  });
501
542
  const ok = await startSession(cwd, opts, state, shutdown);
502
543
  if (!ok) {
503
- process.exit(1);
544
+ emitEvent("degraded-state", {
545
+ reason: "Config invalid or missing at boot. Waiting for valid mindstudio.json."
546
+ });
547
+ log.warn("session", "Booting in degraded state \u2014 no valid config. Watching for changes.");
504
548
  }
505
549
  setupStdinCommands(state, cwd);
506
550
  cleanupConfigWatcher = watchConfigFile(cwd, async () => {
507
- if (stopping || restarting) {
508
- log.debug("session", "Config change ignored (restart in progress)");
509
- return;
510
- }
551
+ if (stopping || restarting) return;
511
552
  restarting = true;
512
553
  try {
513
- log.info("session", "mindstudio.json changed, restarting dev session");
514
554
  emitEvent("config-changed");
555
+ const newConfig = detectAppConfig(cwd);
556
+ if (!newConfig || !newConfig.appId) {
557
+ emitEvent("config-error", {
558
+ message: "mindstudio.json is invalid \u2014 keeping current session"
559
+ });
560
+ log.warn("session", "Config change detected but file is invalid, keeping current session");
561
+ return;
562
+ }
563
+ const wasDegraded = !state.runner;
515
564
  await teardownRunner(state);
516
565
  const ok2 = await startSession(cwd, opts, state, shutdown);
517
- if (ok2 && state.proxy) {
518
- state.proxy.broadcastToClients("reload");
566
+ if (ok2) {
567
+ if (wasDegraded) {
568
+ emitEvent("degraded-state-resolved", { appId: newConfig.appId });
569
+ log.info("session", "Recovered from degraded state");
570
+ }
571
+ if (state.proxy) {
572
+ state.proxy.broadcastToClients("reload");
573
+ }
574
+ } else {
575
+ emitEvent("degraded-state", {
576
+ reason: "Session restart failed after config change. Will retry on next change."
577
+ });
578
+ log.warn("session", "Session restart failed, entering degraded state");
519
579
  }
520
580
  } finally {
521
581
  restarting = false;
@@ -528,4 +588,4 @@ async function startHeadless(opts = {}) {
528
588
  export {
529
589
  startHeadless
530
590
  };
531
- //# sourceMappingURL=chunk-WZCAY73G.js.map
591
+ //# sourceMappingURL=chunk-TSSCMBSI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev/ipc/ipc.ts","../src/dev/ipc/session-events.ts","../src/dev/stdin-commands/run-scenario.ts","../src/dev/stdin-commands/run-method.ts","../src/dev/stdin-commands/impersonate.ts","../src/dev/stdin-commands/browser.ts","../src/dev/stdin-commands/screenshot-full-page.ts","../src/dev/stdin-commands/dev-server-restarting.ts","../src/dev/stdin-commands/browser-status.ts","../src/dev/stdin-commands/reset-browser.ts","../src/dev/stdin-commands/db-query.ts","../src/dev/stdin-commands/index.ts","../src/headless.ts"],"sourcesContent":["/**\n * Central IPC module for headless mode.\n *\n * All stdout writes go through here. Two distinct message types:\n * - System events: unsolicited, no requestId (session lifecycle, connection, etc.)\n * - Command responses: always have requestId + status (started/completed)\n *\n * The caller distinguishes them by the presence of `requestId`.\n */\n\n/**\n * Emit a system event (no requestId).\n * Used for unsolicited events: session lifecycle, connection health, auth, etc.\n */\nexport function emitEvent(event: string, data?: Record<string, unknown>): void {\n process.stdout.write(JSON.stringify({ event, ...data }) + '\\n');\n}\n\n/**\n * Emit a command response (always has requestId).\n * Used for responses to stdin commands.\n */\nexport function emitResponse(\n action: string,\n requestId: string,\n status: 'started' | 'completed',\n data?: Record<string, unknown>,\n): void {\n process.stdout.write(\n JSON.stringify({ event: action, requestId, status, ...data }) + '\\n',\n );\n}\n","/**\n * Subscribe to DevRunner events and relay them as system events to stdout.\n * Only relays genuinely unsolicited events (poll-loop methods, connection, auth).\n * Command responses (scenarios, impersonation) are handled by stdin handlers directly.\n *\n * Returns an array of unsubscribe functions for cleanup on teardown.\n */\n\nimport { devRequestEvents } from './events';\nimport { emitEvent } from './ipc';\n\nexport function subscribeDevEvents(\n shutdown: () => Promise<void>,\n): Array<() => void> {\n const unsubs: Array<() => void> = [];\n\n // Platform-triggered method execution (poll loop)\n unsubs.push(\n devRequestEvents.onStart((event) => {\n emitEvent('platform-method-started', { id: event.id, method: event.method });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onComplete((event) => {\n emitEvent('platform-method-completed', {\n id: event.id,\n success: event.success,\n duration: event.duration,\n ...(event.error ? { error: event.error } : {}),\n });\n }),\n );\n\n // Connection health\n unsubs.push(\n devRequestEvents.onConnectionWarning((message) => {\n emitEvent('connection-lost', { message });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onConnectionRestored(() => {\n emitEvent('connection-restored');\n }),\n );\n\n // Session expiry\n unsubs.push(\n devRequestEvents.onSessionExpired(() => {\n emitEvent('session-expired');\n shutdown().then(() => process.exit(1));\n }),\n );\n\n // Auth refresh\n unsubs.push(\n devRequestEvents.onAuthRefreshStart((url) => {\n emitEvent('auth-refresh-start', { url });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshSuccess(() => {\n emitEvent('auth-refresh-success');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshFailed(() => {\n emitEvent('auth-refresh-failed');\n }),\n );\n\n return unsubs;\n}\n","import { detectAppConfig } from '../config/app-config';\nimport type { CommandContext } from './types';\n\nexport async function handleRunScenario(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const freshConfig = detectAppConfig(ctx.cwd) ?? ctx.state.appConfig;\n const scenario = freshConfig?.scenarios.find((s) => s.id === cmd.scenarioId);\n if (!scenario) throw new Error(`Unknown scenario: ${cmd.scenarioId}`);\n\n const scenarioName = scenario.name ?? scenario.export;\n ctx.started({ scenarioId: scenario.id, name: scenarioName });\n\n const skipTruncate = cmd.skipTruncate === true;\n const result = await ctx.state.runner.runScenario(scenario, { skipTruncate });\n\n return {\n success: result.success,\n scenarioId: scenario.id,\n name: scenarioName,\n ...(result.error ? { error: result.error } : {}),\n };\n}\n","import { detectAppConfig } from '../config/app-config';\nimport type { CommandContext } from './types';\n\nexport async function handleRunMethod(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const methodName = cmd.method as string;\n if (!methodName) throw new Error('run-method requires \"method\" (export name or ID)');\n\n const freshConfig = detectAppConfig(ctx.cwd) ?? ctx.state.appConfig;\n const method =\n freshConfig?.methods.find((m) => m.export === methodName) ??\n freshConfig?.methods.find((m) => m.id === methodName);\n if (!method) throw new Error(`Unknown method: ${methodName}`);\n\n ctx.started({ method: method.export });\n\n const result = await ctx.state.runner.runMethod({\n methodExport: method.export,\n methodPath: method.path,\n input: cmd.input ?? {},\n });\n\n return {\n success: result.success,\n method: method.export,\n output: result.output ?? null,\n error: result.error ?? null,\n stdout: result.stdout ?? [],\n duration: result.duration,\n };\n}\n","import type { CommandContext } from './types';\n\nexport async function handleImpersonate(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const roles = cmd.roles as string[];\n if (!Array.isArray(roles)) throw new Error('impersonate requires roles array');\n\n await ctx.state.runner.setImpersonation(roles);\n return { roles };\n}\n\nexport async function handleClearImpersonation(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n await ctx.state.runner.clearImpersonation();\n return { roles: null };\n}\n","import { getUploadUrl } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleBrowser(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy — browser commands require a web interface');\n\n const steps = cmd.steps as Array<Record<string, unknown>>;\n if (!Array.isArray(steps) || steps.length === 0) {\n throw new Error('browser action requires a non-empty \"steps\" array');\n }\n\n // Inject upload details into any screenshotViewport steps so the browser\n // uploads directly to S3 instead of sending base64 over the WS connection.\n const preparedSteps = await injectScreenshotUploads(ctx, steps);\n\n const result = await ctx.state.proxy.dispatchBrowserCommand(preparedSteps);\n\n // Replace uploaded screenshot results with the public URL\n const resultSteps = (result.steps as Array<Record<string, unknown>>) ?? [];\n for (const step of resultSteps) {\n const stepResult = step.result as Record<string, unknown> | undefined;\n if (stepResult?.uploaded && stepResult?._publicUrl) {\n stepResult.url = stepResult._publicUrl;\n delete stepResult.uploaded;\n delete stepResult._publicUrl;\n delete stepResult.image;\n }\n }\n\n return {\n steps: resultSteps,\n snapshot: result.snapshot,\n logs: result.logs,\n duration: result.duration,\n };\n}\n\n/**\n * For each screenshotViewport step, get a presigned upload URL and attach it.\n * Non-screenshot steps are passed through unchanged.\n */\nasync function injectScreenshotUploads(\n ctx: CommandContext,\n steps: Array<Record<string, unknown>>,\n): Promise<Array<Record<string, unknown>>> {\n const session = ctx.state.runner?.getSession();\n const appId = ctx.state.appConfig?.appId;\n if (!session || !appId) return steps;\n\n const prepared: Array<Record<string, unknown>> = [];\n for (const step of steps) {\n if (step.command === 'screenshotViewport') {\n try {\n const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(\n appId,\n session.sessionId,\n 'jpg',\n 'image/jpeg',\n );\n prepared.push({ ...step, uploadUrl, uploadFields, _publicUrl: publicUrl });\n } catch {\n // If we can't get an upload URL, fall back to inline base64\n prepared.push(step);\n }\n } else {\n prepared.push(step);\n }\n }\n return prepared;\n}\n","import { getUploadUrl } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleScreenshotFullPage(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy');\n if (!ctx.state.proxy.isBrowserConnected()) {\n throw new Error('No browser connected, please refresh the MindStudio preview');\n }\n if (!ctx.state.runner?.getSession() || !ctx.state.appConfig?.appId) {\n throw new Error('No active session');\n }\n\n const startTime = Date.now();\n\n // 1. Get presigned upload URL before dispatching to browser\n const session = ctx.state.runner.getSession()!;\n const { uploadUrl, uploadFields, publicUrl } = await getUploadUrl(\n ctx.state.appConfig.appId,\n session.sessionId,\n 'jpg',\n 'image/jpeg',\n );\n\n // 2. Dispatch to browser — optionally navigate first, then full-page screenshot\n const steps: Array<Record<string, unknown>> = [];\n if (cmd.path) {\n steps.push({ command: 'navigate', url: cmd.path as string });\n }\n steps.push({ command: 'screenshotFullPage', uploadUrl, uploadFields });\n\n const result = await ctx.state.proxy.dispatchBrowserCommand(steps, 120_000);\n\n // The screenshot result is the last step\n const resultSteps = result.steps as Array<Record<string, unknown>>;\n const stepResult = resultSteps?.[resultSteps.length - 1]\n ?.result as { width: number; height: number; uploaded?: boolean } | undefined;\n\n if (!stepResult?.uploaded) {\n throw new Error('Screenshot capture or upload failed');\n }\n\n return {\n url: publicUrl,\n width: stepResult.width,\n height: stepResult.height,\n duration: Date.now() - startTime,\n };\n}\n","import type { CommandContext } from './types';\n\nexport async function handleDevServerRestarting(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy');\n\n ctx.state.proxy.markUpstreamDown();\n return {};\n}\n","import type { CommandContext } from './types';\n\nexport async function handleBrowserStatus(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n return { connected: ctx.state.proxy?.isBrowserConnected() ?? false };\n}\n","import type { CommandContext } from './types';\n\nexport async function handleResetBrowser(\n ctx: CommandContext,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.proxy) throw new Error('No active proxy');\n if (!ctx.state.proxy.isBrowserConnected()) throw new Error('No browser connected');\n\n ctx.state.proxy.broadcastToClients('reload');\n return {};\n}\n","import { getApiBaseUrl } from '../../config';\nimport { fetchCallbackToken } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleDbQuery(\n ctx: CommandContext,\n cmd: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n if (!ctx.state.runner) throw new Error('No active session');\n\n const session = ctx.state.runner.getSession();\n if (!session) throw new Error('No active session');\n\n const sql = cmd.sql as string;\n if (!sql) throw new Error('db-query requires \"sql\"');\n\n // Resolve database — use explicit databaseId, or default to the first one\n let databaseId = cmd.databaseId as string | undefined;\n if (!databaseId) {\n if (session.databases.length === 0) throw new Error('No databases available');\n databaseId = session.databases[0].id;\n }\n\n ctx.started({ databaseId, sql });\n\n const token = await fetchCallbackToken(ctx.state.appConfig!.appId!, session.sessionId);\n const url = `${getApiBaseUrl()}/_internal/v2/db/query`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: token,\n },\n body: JSON.stringify({\n databaseId,\n queries: [{ sql }],\n }),\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Database query failed: ${res.status} ${text}`);\n }\n\n const data = await res.json() as { results: { rows: unknown[]; changes: number }[] };\n\n return {\n success: true,\n databaseId,\n results: data.results,\n };\n}\n","/**\n * Stdin command router for headless mode.\n *\n * Reads NDJSON commands from stdin and dispatches to individual handlers.\n * Every command must include a `requestId` for response correlation.\n * The router wraps handlers with automatic response framing.\n */\n\nimport { emitResponse } from '../ipc/ipc';\nimport { log } from '../logging/logger';\nimport { handleRunScenario } from './run-scenario';\nimport { handleRunMethod } from './run-method';\nimport { handleImpersonate, handleClearImpersonation } from './impersonate';\nimport { handleBrowser } from './browser';\nimport { handleScreenshotFullPage } from './screenshot-full-page';\nimport { handleDevServerRestarting } from './dev-server-restarting';\nimport { handleBrowserStatus } from './browser-status';\nimport { handleResetBrowser } from './reset-browser';\nimport { handleDbQuery } from './db-query';\nimport type { SessionState, CommandContext, CommandHandler } from './types';\n\nexport type { SessionState } from './types';\n\nconst handlers: Record<string, CommandHandler> = {\n 'run-method': handleRunMethod,\n 'run-scenario': handleRunScenario,\n 'impersonate': handleImpersonate,\n 'clear-impersonation': handleClearImpersonation,\n 'browser': handleBrowser,\n 'screenshotFullPage': handleScreenshotFullPage,\n 'browser-status': handleBrowserStatus,\n 'reset-browser': handleResetBrowser,\n 'db-query': handleDbQuery,\n 'dev-server-restarting': handleDevServerRestarting,\n};\n\nexport function setupStdinCommands(\n state: SessionState,\n cwd: string,\n): void {\n if (!process.stdin.readable) return;\n\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let idx: number;\n while ((idx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, idx).trim();\n buffer = buffer.slice(idx + 1);\n if (!line) continue;\n\n let cmd: { action: string; requestId?: string; [key: string]: unknown };\n try {\n cmd = JSON.parse(line);\n } catch {\n log.warn('stdin', 'Invalid JSON on stdin', { preview: line.slice(0, 100) });\n continue;\n }\n\n handleStdinCommand(cmd, state, cwd);\n }\n });\n}\n\nasync function handleStdinCommand(\n cmd: { action: string; requestId?: string; [key: string]: unknown },\n state: SessionState,\n cwd: string,\n): Promise<void> {\n const { requestId, action } = cmd;\n\n if (!requestId) {\n log.warn('stdin', 'Command rejected: missing requestId', { action });\n return;\n }\n\n const handler = handlers[action];\n if (!handler) {\n emitResponse(action ?? 'unknown', requestId, 'completed', {\n success: false,\n error: `Unknown action: ${action}`,\n });\n return;\n }\n\n log.info('stdin', 'Command received', { requestId, action });\n\n const ctx: CommandContext = {\n state,\n cwd,\n requestId,\n started: (data) => emitResponse(action, requestId, 'started', data),\n };\n\n try {\n const result = await handler(ctx, cmd);\n log.info('stdin', 'Command complete', { requestId, action, success: result.success !== false });\n emitResponse(action, requestId, 'completed', result);\n } catch (err) {\n log.warn('stdin', 'Command failed', { requestId, action, error: err instanceof Error ? err.message : String(err) });\n emitResponse(action, requestId, 'completed', {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n","/**\n * Headless Dev Mode\n *\n * Runs the MindStudio dev tunnel without a TUI. Designed for programmatic\n * control by a parent process (e.g., a sandbox C&C server or CI pipeline).\n *\n * Outputs structured JSON events to stdout (one per line, newline-delimited).\n * The parent process reads these to track session state, method execution,\n * errors, and connection health.\n *\n * Does NOT start a dev server — the parent process manages that separately.\n * The tunnel just needs to know which port to proxy to.\n *\n * @module\n */\n\nimport { DevRunner } from './dev/execution/runner';\nimport { DevProxy } from './dev/proxy/proxy';\nimport { syncSchema } from './dev/api';\nimport {\n detectAppConfig,\n getWebInterfaceConfig,\n readTableSources,\n} from './dev/config/app-config';\nimport { initRequestLog, closeRequestLog } from './dev/logging/request-log';\nimport { initBrowserLog, closeBrowserLog } from './dev/logging/browser-log';\nimport { subscribeDevEvents } from './dev/ipc/session-events';\nimport { setupStdinCommands, type SessionState } from './dev/stdin-commands';\nimport { emitEvent } from './dev/ipc/ipc';\nimport {\n getApiKey,\n getApiBaseUrl,\n getUserId,\n getEnvironment,\n getConfigPath,\n} from './config';\nimport { initLoggerHeadless, log, type LogLevel } from './dev/logging/logger';\nimport { stablePort, detectGitBranch } from './dev/utils';\nimport { watchTableFiles } from './dev/config/table-watcher';\nimport { watchConfigFile } from './dev/config/config-watcher';\n\n/**\n * Options for headless dev mode.\n */\nexport interface HeadlessOptions {\n /** Working directory containing mindstudio.json. Defaults to process.cwd(). */\n cwd?: string;\n /** Port the dev server is running on. If omitted, reads from web.json. If neither, proxy is skipped. */\n devPort?: number;\n /** Preferred port for the local proxy. Defaults to a stable port derived from the app ID. */\n proxyPort?: number;\n /** Bind address for the proxy server. Use '0.0.0.0' for hosted sandboxes. Defaults to '127.0.0.1'. */\n bindAddress?: string;\n /** Log level for stderr output. Defaults to 'info'. */\n logLevel?: LogLevel;\n /** URL for the browser agent script. Defaults to unpkg latest. Set to an ngrok URL for development. */\n browserAgentUrl?: string;\n}\n\n\n// ---------------------------------------------------------------------------\n// Session lifecycle\n// ---------------------------------------------------------------------------\n\nasync function startSession(\n cwd: string,\n opts: HeadlessOptions,\n state: SessionState,\n shutdown: () => Promise<void>,\n): Promise<boolean> {\n const bindAddress = opts.bindAddress ?? '127.0.0.1';\n\n // Read fresh config\n const appConfig = detectAppConfig(cwd);\n if (!appConfig) {\n emitEvent('config-error', { message: 'No valid mindstudio.json found in ' + cwd });\n return false;\n }\n\n if (!appConfig.appId) {\n emitEvent('config-error', { message: 'Missing \"appId\" in mindstudio.json' });\n return false;\n }\n\n state.appConfig = appConfig;\n\n // Resolve dev port\n let devPort = opts.devPort ?? null;\n if (devPort === null) {\n const webConfig = getWebInterfaceConfig(appConfig, cwd);\n devPort = webConfig?.devPort ?? null;\n }\n\n emitEvent('session-starting', { appId: appConfig.appId, name: appConfig.name });\n\n try {\n // Start platform session\n const branch = detectGitBranch();\n const runner = new DevRunner(appConfig.appId, cwd, {\n branch,\n methods: appConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path })),\n });\n runner.setAppConfig(appConfig);\n const session = await runner.start();\n state.runner = runner;\n\n // Initialize logs\n initRequestLog(cwd);\n initBrowserLog(cwd);\n\n // Sync schema\n if (appConfig.tables.length > 0) {\n try {\n const tableSources = readTableSources(appConfig, cwd);\n if (tableSources.length > 0) {\n const syncResult = await syncSchema(appConfig.appId, session.sessionId, tableSources);\n session.databases = syncResult.databases;\n emitEvent('schema-sync-completed', {\n created: syncResult.created,\n altered: syncResult.altered,\n errors: syncResult.errors,\n });\n } else {\n log.warn('session', 'No table source files found, skipping schema sync', {\n expected: appConfig.tables.map((t) => t.path),\n });\n }\n } catch (err) {\n emitEvent('schema-sync-completed', {\n created: [],\n altered: [],\n errors: [err instanceof Error ? err.message : 'Schema sync failed'],\n });\n }\n }\n\n // Start or reuse proxy\n if (devPort !== null && session.clientContext) {\n if (state.proxy) {\n // Proxy persists across restarts — just update the context\n state.proxy.updateClientContext(session.clientContext);\n } else {\n const proxy = new DevProxy(devPort, session.clientContext, appConfig.appId, bindAddress, opts.browserAgentUrl);\n const preferred = opts.proxyPort ?? stablePort(appConfig.appId);\n const proxyPort = await proxy.start(preferred);\n state.proxy = proxy;\n state.proxyPort = proxyPort;\n }\n\n runner.setProxyUrl(`http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${state.proxyPort}`);\n runner.setProxy(state.proxy);\n }\n\n emitEvent('session-started', {\n sessionId: session.sessionId,\n releaseId: session.releaseId,\n branch: session.branch,\n proxyPort: state.proxyPort,\n proxyUrl: state.proxyPort\n ? `http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${state.proxyPort}/`\n : null,\n webInterfaceUrl: session.webInterfaceUrl,\n roles: appConfig.roles.map((r) => ({ id: r.id, name: r.name ?? r.id, description: r.description })),\n scenarios: appConfig.scenarios.map((s) => ({\n id: s.id,\n name: s.name ?? s.export,\n description: s.description,\n path: s.path,\n roles: s.roles,\n })),\n });\n\n // Subscribe to runner events\n state.unsubscribers.push(...subscribeDevEvents(shutdown));\n\n // Watch table source files for changes — auto-sync without session restart\n setupTableWatchers(cwd, state);\n\n // Start polling for platform method requests now that schema sync,\n // proxy, and watchers are all set up. Starting earlier would risk\n // executing methods against stale session state (e.g. missing tables).\n runner.startPolling();\n\n return true;\n } catch (err) {\n emitEvent('config-error', {\n message: err instanceof Error ? err.message : 'Failed to start session',\n });\n return false;\n }\n}\n\nfunction setupTableWatchers(cwd: string, state: SessionState): void {\n if (!state.appConfig || state.appConfig.tables.length === 0) return;\n\n const cleanup = watchTableFiles(state.appConfig.tables, cwd, async () => {\n if (!state.runner || !state.appConfig?.appId) return;\n const session = state.runner.getSession();\n if (!session) return;\n\n emitEvent('schema-sync-started');\n log.info('session', 'Table source file changed, syncing schema');\n\n try {\n const tableSources = readTableSources(state.appConfig, cwd);\n if (tableSources.length > 0) {\n const result = await syncSchema(state.appConfig.appId, session.sessionId, tableSources);\n session.databases = result.databases;\n emitEvent('schema-sync-completed', {\n created: result.created,\n altered: result.altered,\n errors: result.errors,\n });\n log.info('session', 'Schema sync complete', { created: result.created, altered: result.altered });\n } else {\n log.warn('session', 'Table source file change detected but file(s) still missing', {\n expected: state.appConfig.tables.map((t) => t.path),\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Schema sync failed';\n emitEvent('schema-sync-completed', { created: [], altered: [], errors: [message] });\n log.warn('session', 'Schema sync failed', { error: message });\n }\n });\n\n state.unsubscribers.push(cleanup);\n}\n\n/** Tear down the runner, logs, and watchers. Proxy stays alive for reuse. */\nasync function teardownRunner(state: SessionState): Promise<void> {\n for (const unsub of state.unsubscribers) unsub();\n state.unsubscribers = [];\n\n if (state.runner) {\n await state.runner.stop().catch(() => {});\n state.runner = null;\n }\n\n closeRequestLog();\n closeBrowserLog();\n}\n\n/** Full teardown including proxy. Used on process shutdown. */\nasync function teardownAll(state: SessionState): Promise<void> {\n await teardownRunner(state);\n\n state.proxy?.stop();\n state.proxy = null;\n state.proxyPort = null;\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Start the dev tunnel in headless mode.\n */\nexport async function startHeadless(opts: HeadlessOptions = {}): Promise<void> {\n initLoggerHeadless(opts.logLevel ?? 'info');\n\n const cwd = opts.cwd ?? process.cwd();\n\n const apiKey = getApiKey();\n const userId = getUserId();\n log.info('session', 'Startup config', {\n configPath: getConfigPath(),\n environment: getEnvironment(),\n apiBaseUrl: getApiBaseUrl(),\n hasApiKey: !!apiKey,\n apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + '...' : null,\n hasUserId: !!userId,\n userId: userId ?? null,\n cwd,\n });\n\n const state: SessionState = {\n runner: null,\n proxy: null,\n appConfig: null,\n proxyPort: null,\n unsubscribers: [],\n };\n\n let restarting = false;\n let cleanupConfigWatcher: (() => void) | undefined;\n\n let stopping = false;\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n emitEvent('session-stopping');\n cleanupConfigWatcher?.();\n await teardownAll(state);\n emitEvent('session-stopped');\n };\n\n process.on('SIGTERM', () => { shutdown().then(() => process.exit(0)); });\n process.on('SIGINT', () => { shutdown().then(() => process.exit(0)); });\n\n // Initial session start — enter degraded state if config is invalid so the\n // agent/user can fix mindstudio.json and the watcher will pick it up.\n const ok = await startSession(cwd, opts, state, shutdown);\n if (!ok) {\n emitEvent('degraded-state', {\n reason: 'Config invalid or missing at boot. Waiting for valid mindstudio.json.',\n });\n log.warn('session', 'Booting in degraded state — no valid config. Watching for changes.');\n }\n\n // Stdin command loop\n setupStdinCommands(state, cwd);\n\n // Watch mindstudio.json for changes — validate before teardown so corrupt\n // writes don't kill a running session. In degraded state (no runner), a\n // valid config triggers a fresh startSession.\n cleanupConfigWatcher = watchConfigFile(cwd, async () => {\n if (stopping || restarting) return;\n restarting = true;\n try {\n emitEvent('config-changed');\n\n // Validate BEFORE tearing down the running session\n const newConfig = detectAppConfig(cwd);\n if (!newConfig || !newConfig.appId) {\n emitEvent('config-error', {\n message: 'mindstudio.json is invalid — keeping current session',\n });\n log.warn('session', 'Config change detected but file is invalid, keeping current session');\n return;\n }\n\n const wasDegraded = !state.runner;\n await teardownRunner(state);\n const ok = await startSession(cwd, opts, state, shutdown);\n if (ok) {\n if (wasDegraded) {\n emitEvent('degraded-state-resolved', { appId: newConfig.appId });\n log.info('session', 'Recovered from degraded state');\n }\n if (state.proxy) {\n state.proxy.broadcastToClients('reload');\n }\n } else {\n emitEvent('degraded-state', {\n reason: 'Session restart failed after config change. Will retry on next change.',\n });\n log.warn('session', 'Session restart failed, entering degraded state');\n }\n } finally {\n restarting = false;\n }\n });\n\n // Keep the process alive — the poll loop runs in DevRunner\n await new Promise<void>(() => {});\n}\n"],"mappings":"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","names":["ok"]}
@@ -384,7 +384,9 @@ function logMethodExecution(entry) {
384
384
  output: entry.result.output ?? null,
385
385
  error: entry.result.error ?? null,
386
386
  stdout: entry.result.stdout ?? [],
387
+ context: entry.context ?? null,
387
388
  duration: entry.duration,
389
+ timing: entry.timing ?? null,
388
390
  stats: entry.result.stats ?? null
389
391
  });
390
392
  }
@@ -1179,6 +1181,7 @@ var DevRunner = class {
1179
1181
  methodPath: opts.methodPath,
1180
1182
  input: opts.input,
1181
1183
  authorizationToken,
1184
+ context: { auth, databases: this.session.databases },
1182
1185
  databases: this.session.databases,
1183
1186
  result,
1184
1187
  duration
@@ -1210,7 +1213,7 @@ var DevRunner = class {
1210
1213
  }
1211
1214
  // Run a scenario: truncate tables → execute seed → impersonate roles.
1212
1215
  // Called directly (not via poll loop) by the TUI or headless stdin.
1213
- async runScenario(scenario) {
1216
+ async runScenario(scenario, opts) {
1214
1217
  if (!this.session || !this.transpiler) {
1215
1218
  return { success: false, databases: [], error: "Session not started" };
1216
1219
  }
@@ -1218,9 +1221,11 @@ var DevRunner = class {
1218
1221
  const scenarioName = scenario.name ?? scenario.export;
1219
1222
  log.info("runner", "Scenario starting", { id: scenario.id, name: scenarioName });
1220
1223
  try {
1221
- log.debug("runner", "Resetting database for scenario");
1222
- const databases = await resetDevDatabase(this.appId, this.session.sessionId, "truncate");
1223
- this.session.databases = databases;
1224
+ if (!opts?.skipTruncate) {
1225
+ log.debug("runner", "Resetting database for scenario");
1226
+ const databases2 = await resetDevDatabase(this.appId, this.session.sessionId, "truncate");
1227
+ this.session.databases = databases2;
1228
+ }
1224
1229
  log.debug("runner", "Transpiling scenario", { path: scenario.path });
1225
1230
  const transpiledPath = await this.transpiler.transpile(scenario.path);
1226
1231
  const authorizationToken = await fetchCallbackToken(this.appId, this.session.sessionId);
@@ -1328,6 +1333,10 @@ var DevRunner = class {
1328
1333
  await this.handleGetAgentConfig(request);
1329
1334
  return;
1330
1335
  }
1336
+ if (request.type === "get-auth-config") {
1337
+ await this.handleGetAuthConfig(request);
1338
+ return;
1339
+ }
1331
1340
  const startTime = Date.now();
1332
1341
  const method = this.appConfig?.methods.find((m) => m.id === request.methodId);
1333
1342
  if (!method) {
@@ -1352,15 +1361,15 @@ var DevRunner = class {
1352
1361
  });
1353
1362
  log.info("runner", "Method received", { requestId: request.requestId, method: method.export, source: "poll", sessionId: this.session.sessionId });
1354
1363
  try {
1364
+ const t0 = Date.now();
1355
1365
  const transpiledPath = await this.transpiler.transpile(method.path);
1366
+ const t1 = Date.now();
1367
+ const userId = request.userId ?? null;
1356
1368
  const roles = request.roleOverride ?? this.roleOverride;
1357
- const auth = roles ? {
1358
- userId: this.session.auth.userId,
1359
- roleAssignments: roles.map((roleName) => ({
1360
- userId: this.session.auth.userId,
1361
- roleName
1362
- }))
1363
- } : this.session.auth;
1369
+ const auth = {
1370
+ userId,
1371
+ roleAssignments: roles ? roles.map((roleName) => ({ userId, roleName })) : []
1372
+ };
1364
1373
  const result = await executeMethod({
1365
1374
  transpiledPath,
1366
1375
  methodExport: method.export,
@@ -1372,6 +1381,7 @@ var DevRunner = class {
1372
1381
  projectRoot: this.projectRoot,
1373
1382
  streamId: request.streamId
1374
1383
  });
1384
+ const t2 = Date.now();
1375
1385
  const devResult = {
1376
1386
  type: "execute",
1377
1387
  success: result.success,
@@ -1386,14 +1396,21 @@ var DevRunner = class {
1386
1396
  request.requestId,
1387
1397
  devResult
1388
1398
  );
1399
+ const t3 = Date.now();
1389
1400
  const duration = Date.now() - startTime;
1401
+ const timing = {
1402
+ transpileMs: t1 - t0,
1403
+ executeMs: t2 - t1,
1404
+ submitMs: t3 - t2,
1405
+ totalMs: duration
1406
+ };
1390
1407
  if (result.success) {
1391
- log.info("runner", "Method complete", { requestId: request.requestId, method: method.export, duration, sessionId: this.session.sessionId });
1408
+ log.info("runner", "Method complete", { requestId: request.requestId, method: method.export, timing, sessionId: this.session.sessionId });
1392
1409
  } else {
1393
1410
  log.warn("runner", "Method failed", {
1394
1411
  requestId: request.requestId,
1395
1412
  method: method.export,
1396
- duration,
1413
+ timing,
1397
1414
  error: result.error ? formatErrorForDisplay(result.error) : void 0,
1398
1415
  sessionId: this.session.sessionId
1399
1416
  });
@@ -1406,9 +1423,11 @@ var DevRunner = class {
1406
1423
  input: request.input,
1407
1424
  roleOverride: request.roleOverride,
1408
1425
  authorizationToken: request.authorizationToken,
1426
+ context: { auth, databases: this.session.databases },
1409
1427
  databases: this.session.databases,
1410
1428
  result,
1411
- duration
1429
+ duration,
1430
+ timing
1412
1431
  });
1413
1432
  devRequestEvents.emitComplete({
1414
1433
  id: request.requestId,
@@ -1492,6 +1511,42 @@ var DevRunner = class {
1492
1511
  }
1493
1512
  }
1494
1513
  }
1514
+ async handleGetAuthConfig(request) {
1515
+ log.info("runner", "Auth config requested", { requestId: request.requestId, sessionId: this.session.sessionId });
1516
+ try {
1517
+ if (!this.appConfig) {
1518
+ throw new Error("App config not available");
1519
+ }
1520
+ await submitDevResult(
1521
+ this.appId,
1522
+ this.session.sessionId,
1523
+ request.requestId,
1524
+ {
1525
+ type: "get-auth-config",
1526
+ success: true,
1527
+ output: { auth: this.appConfig.auth ?? null, name: this.appConfig.name }
1528
+ }
1529
+ );
1530
+ log.info("runner", "Auth config sent", { requestId: request.requestId });
1531
+ } catch (err) {
1532
+ const message = err instanceof Error ? err.message : "Unknown error";
1533
+ log.error("runner", "Auth config failed", { requestId: request.requestId, error: message });
1534
+ try {
1535
+ await submitDevResult(
1536
+ this.appId,
1537
+ this.session.sessionId,
1538
+ request.requestId,
1539
+ {
1540
+ type: "get-auth-config",
1541
+ success: false,
1542
+ error: { message }
1543
+ }
1544
+ );
1545
+ } catch (submitErr) {
1546
+ log.error("runner", "Failed to report auth config error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
1547
+ }
1548
+ }
1549
+ }
1495
1550
  /**
1496
1551
  * Attempt to refresh expired auth credentials via the device auth flow.
1497
1552
  * Opens the browser for the user to re-authorize, polls for the new token.
@@ -2127,6 +2182,12 @@ var DevProxy = class _DevProxy {
2127
2182
  (proxyRes) => {
2128
2183
  const responseHeaders = { ...proxyRes.headers, ...cors };
2129
2184
  responseHeaders["cache-control"] = "no-store";
2185
+ if (responseHeaders["set-cookie"]) {
2186
+ const cookies = Array.isArray(responseHeaders["set-cookie"]) ? responseHeaders["set-cookie"] : [responseHeaders["set-cookie"]];
2187
+ responseHeaders["set-cookie"] = cookies.map(
2188
+ (c) => c.replace(/;\s*[Dd]omain=[^;]*/g, "").replace(/;\s*[Ss]ame[Ss]ite=[^;]*/g, "; SameSite=None")
2189
+ );
2190
+ }
2130
2191
  clientRes.writeHead(proxyRes.statusCode ?? 502, responseHeaders);
2131
2192
  proxyRes.pipe(clientRes);
2132
2193
  }
@@ -2165,9 +2226,22 @@ var DevProxy = class _DevProxy {
2165
2226
  if (isHtml) {
2166
2227
  const chunks = [];
2167
2228
  upstreamRes.on("data", (chunk) => chunks.push(chunk));
2168
- upstreamRes.on("end", () => {
2229
+ upstreamRes.on("end", async () => {
2169
2230
  let html = Buffer.concat(chunks).toString("utf-8");
2170
- html = this.injectScripts(html);
2231
+ const authCookie = _DevProxy.parseAuthCookie(clientReq.headers.cookie);
2232
+ let contextOverride;
2233
+ if (authCookie) {
2234
+ const resolved = await this.resolveAuthCookie(authCookie);
2235
+ if (resolved) {
2236
+ contextOverride = {
2237
+ ...this.clientContext,
2238
+ user: resolved.user,
2239
+ token: resolved.token,
2240
+ methods: resolved.methods
2241
+ };
2242
+ }
2243
+ }
2244
+ html = this.injectScripts(html, contextOverride);
2171
2245
  const headers = {
2172
2246
  ...upstreamRes.headers,
2173
2247
  ...cors,
@@ -2352,11 +2426,67 @@ var DevProxy = class _DevProxy {
2352
2426
  );
2353
2427
  }
2354
2428
  }
2429
+ /**
2430
+ * Parse __ms_auth cookie value from a raw Cookie header.
2431
+ */
2432
+ static parseAuthCookie(cookieHeader) {
2433
+ if (!cookieHeader) return null;
2434
+ const match = cookieHeader.match(/(?:^|;\s*)__ms_auth=([^;]+)/);
2435
+ return match ? match[1] : null;
2436
+ }
2437
+ /**
2438
+ * Resolve __ms_auth cookie to an authenticated context via the platform.
2439
+ * Returns { user, token, methods } or null if not authenticated.
2440
+ * Results are cached in memory — invalidated when auth endpoints set new cookies.
2441
+ */
2442
+ async resolveAuthCookie(cookie) {
2443
+ const apiBaseUrl = getApiBaseUrl();
2444
+ const url = new URL(`/_internal/v2/apps/${this.appId}/auth/me`, apiBaseUrl);
2445
+ const isHttps = url.protocol === "https:";
2446
+ const httpModule = isHttps ? https : http;
2447
+ return new Promise((resolve2) => {
2448
+ const req = httpModule.request(
2449
+ {
2450
+ hostname: url.hostname,
2451
+ port: url.port || (isHttps ? 443 : 80),
2452
+ path: url.pathname,
2453
+ method: "GET",
2454
+ headers: {
2455
+ cookie: `__ms_auth=${cookie}`,
2456
+ host: url.host
2457
+ }
2458
+ },
2459
+ (res) => {
2460
+ const chunks = [];
2461
+ res.on("data", (chunk) => chunks.push(chunk));
2462
+ res.on("end", () => {
2463
+ try {
2464
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
2465
+ if (body.user && body.token) {
2466
+ resolve2({ user: body.user, token: body.token, methods: body.methods ?? {} });
2467
+ } else {
2468
+ resolve2(null);
2469
+ }
2470
+ } catch {
2471
+ resolve2(null);
2472
+ }
2473
+ });
2474
+ }
2475
+ );
2476
+ req.on("error", () => resolve2(null));
2477
+ req.setTimeout(3e3, () => {
2478
+ req.destroy();
2479
+ resolve2(null);
2480
+ });
2481
+ req.end();
2482
+ });
2483
+ }
2355
2484
  /**
2356
2485
  * Inject window.__MINDSTUDIO__ context and browser agent script tag into HTML.
2357
2486
  */
2358
- injectScripts(html) {
2359
- const contextScript = `<script>window.__MINDSTUDIO__=${JSON.stringify(this.clientContext)};</script>`;
2487
+ injectScripts(html, contextOverride) {
2488
+ const context = contextOverride ?? this.clientContext;
2489
+ const contextScript = `<script>window.__MINDSTUDIO__=${JSON.stringify(context)};</script>`;
2360
2490
  const agentUrl = this.browserAgentUrl || "https://unpkg.com/@mindstudio-ai/browser-agent/dist/index.js";
2361
2491
  const agentScript = `<script async src="${agentUrl}"></script>`;
2362
2492
  const injection = `${contextScript}
@@ -2424,6 +2554,7 @@ function detectAppConfig(cwd = process.cwd()) {
2424
2554
  appId: parsed.appId,
2425
2555
  name: parsed.name,
2426
2556
  description: parsed.description,
2557
+ auth: parsed.auth ?? void 0,
2427
2558
  roles: parsed.roles ?? [],
2428
2559
  tables: parsed.tables ?? [],
2429
2560
  methods: parsed.methods,
@@ -2622,6 +2753,7 @@ export {
2622
2753
  initLoggerHeadless,
2623
2754
  initLoggerInteractive,
2624
2755
  syncSchema,
2756
+ fetchCallbackToken,
2625
2757
  getUploadUrl,
2626
2758
  devRequestEvents,
2627
2759
  pollForRequest,
@@ -2650,4 +2782,4 @@ export {
2650
2782
  watchTableFiles,
2651
2783
  watchConfigFile
2652
2784
  };
2653
- //# sourceMappingURL=chunk-C64JURPM.js.map
2785
+ //# sourceMappingURL=chunk-U6BBTRV3.js.map