@mindstudio-ai/local-model-tunnel 0.5.42 → 0.5.44

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.
@@ -24,7 +24,7 @@ import {
24
24
  syncSchema,
25
25
  watchConfigFile,
26
26
  watchTableFiles
27
- } from "./chunk-6LWKL7BC.js";
27
+ } from "./chunk-XUR5YLIV.js";
28
28
 
29
29
  // src/dev/ipc/ipc.ts
30
30
  function emitEvent(event, data) {
@@ -561,9 +561,14 @@ async function startHeadless(opts = {}) {
561
561
  let restarting = false;
562
562
  let cleanupConfigWatcher;
563
563
  let stopping = false;
564
+ let degradedRetryTimer = null;
564
565
  const shutdown = async () => {
565
566
  if (stopping) return;
566
567
  stopping = true;
568
+ if (degradedRetryTimer) {
569
+ clearInterval(degradedRetryTimer);
570
+ degradedRetryTimer = null;
571
+ }
567
572
  emitEvent("session-stopping");
568
573
  cleanupConfigWatcher?.();
569
574
  await teardownAll(state);
@@ -575,12 +580,46 @@ async function startHeadless(opts = {}) {
575
580
  process.on("SIGINT", () => {
576
581
  shutdown().then(() => process.exit(0));
577
582
  });
578
- const ok = await startSession(cwd, opts, state, shutdown);
579
- if (!ok) {
583
+ const MAX_START_RETRIES = 5;
584
+ let started = false;
585
+ for (let attempt = 1; attempt <= MAX_START_RETRIES && !stopping; attempt++) {
586
+ started = await startSession(cwd, opts, state, shutdown);
587
+ if (started) break;
588
+ if (attempt < MAX_START_RETRIES) {
589
+ const delay = Math.min(1e3 * 2 ** (attempt - 1), 1e4);
590
+ log.info("session", `Start failed, retrying in ${delay}ms`, { attempt, maxAttempts: MAX_START_RETRIES });
591
+ await new Promise((r) => setTimeout(r, delay));
592
+ }
593
+ }
594
+ if (!started && !stopping) {
580
595
  emitEvent("degraded-state", {
581
596
  reason: "Config invalid or missing at boot. Waiting for valid mindstudio.json."
582
597
  });
583
598
  log.warn("session", "Booting in degraded state \u2014 no valid config. Watching for changes.");
599
+ degradedRetryTimer = setInterval(async () => {
600
+ if (stopping || restarting || state.runner) {
601
+ if (state.runner && degradedRetryTimer) {
602
+ clearInterval(degradedRetryTimer);
603
+ degradedRetryTimer = null;
604
+ }
605
+ return;
606
+ }
607
+ restarting = true;
608
+ try {
609
+ log.info("session", "Retrying session start from degraded state");
610
+ const ok = await startSession(cwd, opts, state, shutdown);
611
+ if (ok) {
612
+ emitEvent("degraded-state-resolved", { appId: state.appConfig?.appId });
613
+ log.info("session", "Recovered from degraded state");
614
+ if (degradedRetryTimer) {
615
+ clearInterval(degradedRetryTimer);
616
+ degradedRetryTimer = null;
617
+ }
618
+ }
619
+ } finally {
620
+ restarting = false;
621
+ }
622
+ }, 15e3);
584
623
  }
585
624
  setupStdinCommands(state, cwd);
586
625
  cleanupConfigWatcher = watchConfigFile(cwd, async () => {
@@ -598,11 +637,15 @@ async function startHeadless(opts = {}) {
598
637
  }
599
638
  const wasDegraded = !state.runner;
600
639
  await teardownRunner(state);
601
- const ok2 = await startSession(cwd, opts, state, shutdown);
602
- if (ok2) {
640
+ const ok = await startSession(cwd, opts, state, shutdown);
641
+ if (ok) {
603
642
  if (wasDegraded) {
604
643
  emitEvent("degraded-state-resolved", { appId: newConfig.appId });
605
644
  log.info("session", "Recovered from degraded state");
645
+ if (degradedRetryTimer) {
646
+ clearInterval(degradedRetryTimer);
647
+ degradedRetryTimer = null;
648
+ }
606
649
  }
607
650
  if (state.proxy) {
608
651
  state.proxy.broadcastToClients("reload");
@@ -624,4 +667,4 @@ async function startHeadless(opts = {}) {
624
667
  export {
625
668
  startHeadless
626
669
  };
627
- //# sourceMappingURL=chunk-RXFQETVJ.js.map
670
+ //# sourceMappingURL=chunk-7DD5SY5V.js.map
@@ -1 +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/setup-browser.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 // Reset the browser so it picks up the new data/roles from the scenario\n if (result.success && ctx.state.proxy?.isBrowserConnected()) {\n ctx.state.proxy.broadcastToClients('reload');\n }\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; styleMap?: string } | 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 ...(stepResult.styleMap ? { styleMap: stepResult.styleMap } : {}),\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 const appId = ctx.state.appConfig?.appId;\n if (!appId) throw new Error('No app config available');\n\n ctx.started({ databaseId, sql });\n\n const token = await fetchCallbackToken(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","import { createAuthSession } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleSetupBrowser(\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.appConfig?.appId) throw new Error('No active session');\n\n const auth = cmd.auth as { email?: string; phone?: string; roles?: string[] } | undefined;\n const path = (cmd.path as string) || '/';\n\n const steps: Array<Record<string, unknown>> = [];\n\n // 1. Mint auth cookie and inject it into the browser\n if (auth) {\n const { cookie } = await createAuthSession(ctx.state.appConfig.appId, auth);\n steps.push({\n command: 'evaluate',\n script: `document.cookie = '__ms_auth=${cookie}; Path=/; Secure; SameSite=None'`,\n });\n // Hard reload so the proxy resolves the cookie via /auth/me\n // and injects authenticated window.__MINDSTUDIO__\n steps.push({ command: 'reload' });\n }\n\n // 2. Navigate to target path (SPA nav — auth is already injected after reload)\n if (path !== '/') {\n steps.push({ command: 'navigate', url: path });\n }\n\n // Trailing snapshot ensures the command completes through the\n // stash/resume path after reload (reload kills the page; remaining\n // steps are stashed in sessionStorage and resumed on reconnect).\n if (steps.length > 0) {\n steps.push({ command: 'snapshot' });\n await ctx.state.proxy.dispatchBrowserCommand(steps);\n }\n\n return { success: true, path, authenticated: !!auth };\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 { handleSetupBrowser } from './setup-browser';\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 'setup-browser': handleSetupBrowser,\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"]}
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/setup-browser.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 // Reset the browser so it picks up the new data/roles from the scenario\n if (result.success && ctx.state.proxy?.isBrowserConnected()) {\n ctx.state.proxy.broadcastToClients('reload');\n }\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; styleMap?: string } | 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 ...(stepResult.styleMap ? { styleMap: stepResult.styleMap } : {}),\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 const appId = ctx.state.appConfig?.appId;\n if (!appId) throw new Error('No app config available');\n\n ctx.started({ databaseId, sql });\n\n const token = await fetchCallbackToken(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","import { createAuthSession } from '../api';\nimport type { CommandContext } from './types';\n\nexport async function handleSetupBrowser(\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.appConfig?.appId) throw new Error('No active session');\n\n const auth = cmd.auth as { email?: string; phone?: string; roles?: string[] } | undefined;\n const path = (cmd.path as string) || '/';\n\n const steps: Array<Record<string, unknown>> = [];\n\n // 1. Mint auth cookie and inject it into the browser\n if (auth) {\n const { cookie } = await createAuthSession(ctx.state.appConfig.appId, auth);\n steps.push({\n command: 'evaluate',\n script: `document.cookie = '__ms_auth=${cookie}; Path=/; Secure; SameSite=None'`,\n });\n // Hard reload so the proxy resolves the cookie via /auth/me\n // and injects authenticated window.__MINDSTUDIO__\n steps.push({ command: 'reload' });\n }\n\n // 2. Navigate to target path (SPA nav — auth is already injected after reload)\n if (path !== '/') {\n steps.push({ command: 'navigate', url: path });\n }\n\n // Trailing snapshot ensures the command completes through the\n // stash/resume path after reload (reload kills the page; remaining\n // steps are stashed in sessionStorage and resumed on reconnect).\n if (steps.length > 0) {\n steps.push({ command: 'snapshot' });\n await ctx.state.proxy.dispatchBrowserCommand(steps);\n }\n\n return { success: true, path, authenticated: !!auth };\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 { handleSetupBrowser } from './setup-browser';\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 'setup-browser': handleSetupBrowser,\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 let degradedRetryTimer: ReturnType<typeof setInterval> | null = null;\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n if (degradedRetryTimer) { clearInterval(degradedRetryTimer); degradedRetryTimer = null; }\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 — retry a few times with backoff before degrading.\n // Snapshot resumes often hit a transient 400 from /manage/start because the\n // platform-side session state is stale. A short retry usually recovers.\n const MAX_START_RETRIES = 5;\n let started = false;\n for (let attempt = 1; attempt <= MAX_START_RETRIES && !stopping; attempt++) {\n started = await startSession(cwd, opts, state, shutdown);\n if (started) break;\n if (attempt < MAX_START_RETRIES) {\n const delay = Math.min(1000 * 2 ** (attempt - 1), 10_000);\n log.info('session', `Start failed, retrying in ${delay}ms`, { attempt, maxAttempts: MAX_START_RETRIES });\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n\n if (!started && !stopping) {\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 // Periodically retry in degraded state (covers transient platform issues\n // that outlast the initial retry window, e.g. long snapshot resume).\n degradedRetryTimer = setInterval(async () => {\n if (stopping || restarting || state.runner) {\n if (state.runner && degradedRetryTimer) {\n clearInterval(degradedRetryTimer);\n degradedRetryTimer = null;\n }\n return;\n }\n restarting = true;\n try {\n log.info('session', 'Retrying session start from degraded state');\n const ok = await startSession(cwd, opts, state, shutdown);\n if (ok) {\n emitEvent('degraded-state-resolved', { appId: state.appConfig?.appId });\n log.info('session', 'Recovered from degraded state');\n if (degradedRetryTimer) {\n clearInterval(degradedRetryTimer);\n degradedRetryTimer = null;\n }\n }\n } finally {\n restarting = false;\n }\n }, 15_000);\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 if (degradedRetryTimer) { clearInterval(degradedRetryTimer); degradedRetryTimer = null; }\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":[]}
@@ -698,8 +698,16 @@ console.error = (...args) => {
698
698
  _origError(...args);
699
699
  };
700
700
 
701
+ // Track secret keys so we can clean up between requests
702
+ let _activeSecretKeys = [];
703
+
701
704
  process.on('message', async (msg) => {
702
- const { id, transpiledPath, methodExport, input, auth, databases, authorizationToken, apiBaseUrl, streamId } = msg;
705
+ const { id, transpiledPath, methodExport, input, auth, databases, authorizationToken, apiBaseUrl, streamId, secrets } = msg;
706
+
707
+ // Apply per-request secrets to process.env (clean up previous first)
708
+ for (const key of _activeSecretKeys) delete process.env[key];
709
+ _activeSecretKeys = secrets ? Object.keys(secrets) : [];
710
+ if (secrets) Object.assign(process.env, secrets);
703
711
 
704
712
  const ctx = {
705
713
  callbackToken: authorizationToken,
@@ -784,8 +792,11 @@ const _origLog = console.log;
784
792
  const _origWarn = console.warn;
785
793
  const _origError = console.error;
786
794
 
795
+ // Track secret keys so we can clean up between requests
796
+ let _activeSecretKeys = [];
797
+
787
798
  process.on('message', async (msg) => {
788
- const { id, transpiledPath, methodExport, input, auth, databases, authorizationToken, apiBaseUrl, streamId } = msg;
799
+ const { id, transpiledPath, methodExport, input, auth, databases, authorizationToken, apiBaseUrl, streamId, secrets } = msg;
789
800
 
790
801
  // Update per-request env vars
791
802
  process.env.CALLBACK_TOKEN = authorizationToken;
@@ -793,6 +804,11 @@ process.on('message', async (msg) => {
793
804
  if (streamId) process.env.STREAM_ID = streamId;
794
805
  else delete process.env.STREAM_ID;
795
806
 
807
+ // Apply per-request secrets to process.env (clean up previous first)
808
+ for (const key of _activeSecretKeys) delete process.env[key];
809
+ _activeSecretKeys = secrets ? Object.keys(secrets) : [];
810
+ if (secrets) Object.assign(process.env, secrets);
811
+
796
812
  // Update global context
797
813
  global.ai = { auth, databases };
798
814
 
@@ -964,7 +980,8 @@ async function executeMethodInWorker(opts) {
964
980
  databases: opts.databases,
965
981
  authorizationToken: opts.authorizationToken,
966
982
  apiBaseUrl: opts.apiBaseUrl,
967
- streamId: opts.streamId
983
+ streamId: opts.streamId,
984
+ secrets: opts.secrets
968
985
  });
969
986
  });
970
987
  }
@@ -1150,6 +1167,8 @@ async function disconnectHeartbeat() {
1150
1167
  }
1151
1168
 
1152
1169
  // src/dev/execution/runner.ts
1170
+ import { readFileSync as readFileSync3 } from "fs";
1171
+ import { join as join6 } from "path";
1153
1172
  import { randomBytes as randomBytes2 } from "crypto";
1154
1173
 
1155
1174
  // src/dev/execution/format-error.ts
@@ -1536,6 +1555,10 @@ var DevRunner = class {
1536
1555
  await this.handleGetAuthConfig(request);
1537
1556
  return;
1538
1557
  }
1558
+ if (request.type === "get-api-config") {
1559
+ await this.handleGetApiConfig(request);
1560
+ return;
1561
+ }
1539
1562
  const startTime = Date.now();
1540
1563
  const method = this.appConfig?.methods.find((m) => m.id === request.methodId);
1541
1564
  if (!method) {
@@ -1564,10 +1587,10 @@ var DevRunner = class {
1564
1587
  const transpiledPath = await this.transpiler.transpile(method.path);
1565
1588
  const t1 = Date.now();
1566
1589
  const userId = request.userId ?? null;
1567
- const roles = request.roleOverride ?? this.roleOverride;
1590
+ const overrideRoles = request.roleOverride ?? this.roleOverride;
1568
1591
  const auth = {
1569
1592
  userId,
1570
- roleAssignments: roles ? roles.map((roleName) => ({ userId, roleName })) : []
1593
+ roleAssignments: overrideRoles ? overrideRoles.map((roleName) => ({ userId, roleName })) : request.roleAssignments ?? []
1571
1594
  };
1572
1595
  const result = await executeMethod({
1573
1596
  transpiledPath,
@@ -1579,7 +1602,8 @@ var DevRunner = class {
1579
1602
  apiBaseUrl: getApiBaseUrl(),
1580
1603
  projectRoot: this.projectRoot,
1581
1604
  sessionId: this.session.sessionId,
1582
- streamId: request.streamId
1605
+ streamId: request.streamId,
1606
+ secrets: request.secrets
1583
1607
  });
1584
1608
  const t2 = Date.now();
1585
1609
  const devResult = {
@@ -1747,6 +1771,51 @@ var DevRunner = class {
1747
1771
  }
1748
1772
  }
1749
1773
  }
1774
+ async handleGetApiConfig(request) {
1775
+ log.info("runner", "API config requested", { requestId: request.requestId, sessionId: this.session.sessionId });
1776
+ try {
1777
+ const apiInterface = this.appConfig?.interfaces.find(
1778
+ (i) => i.type === "api" && i.enabled !== false
1779
+ );
1780
+ if (!apiInterface) {
1781
+ throw new Error("No API interface config found");
1782
+ }
1783
+ const apiJsonPath = join6(this.projectRoot, apiInterface.path);
1784
+ const raw = readFileSync3(apiJsonPath, "utf-8");
1785
+ const parsed = JSON.parse(raw);
1786
+ if (!parsed.api) {
1787
+ throw new Error("No API interface config found");
1788
+ }
1789
+ await submitDevResult(
1790
+ this.appId,
1791
+ this.session.sessionId,
1792
+ request.requestId,
1793
+ {
1794
+ type: "get-api-config",
1795
+ success: true,
1796
+ output: parsed.api
1797
+ }
1798
+ );
1799
+ log.info("runner", "API config sent", { requestId: request.requestId });
1800
+ } catch (err) {
1801
+ const message = err instanceof Error ? err.message : "Unknown error";
1802
+ log.error("runner", "API config failed", { requestId: request.requestId, error: message });
1803
+ try {
1804
+ await submitDevResult(
1805
+ this.appId,
1806
+ this.session.sessionId,
1807
+ request.requestId,
1808
+ {
1809
+ type: "get-api-config",
1810
+ success: false,
1811
+ error: { message }
1812
+ }
1813
+ );
1814
+ } catch (submitErr) {
1815
+ log.error("runner", "Failed to report API config error to platform", { error: submitErr instanceof Error ? submitErr.message : String(submitErr) });
1816
+ }
1817
+ }
1818
+ }
1750
1819
  /**
1751
1820
  * Attempt to refresh expired auth credentials via the device auth flow.
1752
1821
  * Opens the browser for the user to re-authorize, polls for the new token.
@@ -2444,6 +2513,9 @@ var DevProxy = class _DevProxy {
2444
2513
  };
2445
2514
  delete headers["connection"];
2446
2515
  delete headers["accept-encoding"];
2516
+ if (originalPath.startsWith("/_/api/") && this.clientContext.releaseId) {
2517
+ headers["x-dev-session"] = this.clientContext.releaseId;
2518
+ }
2447
2519
  const proxyReq = httpModule.request(
2448
2520
  {
2449
2521
  hostname: target.hostname,
@@ -2861,13 +2933,13 @@ ${agentScript}`;
2861
2933
  };
2862
2934
 
2863
2935
  // src/dev/config/app-config.ts
2864
- import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
2865
- import { join as join6, dirname as dirname3 } from "path";
2936
+ import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
2937
+ import { join as join7, dirname as dirname3 } from "path";
2866
2938
  function detectAppConfig(cwd = process.cwd()) {
2867
- const appJsonPath = join6(cwd, "mindstudio.json");
2939
+ const appJsonPath = join7(cwd, "mindstudio.json");
2868
2940
  if (!existsSync2(appJsonPath)) return null;
2869
2941
  try {
2870
- const raw = readFileSync3(appJsonPath, "utf-8");
2942
+ const raw = readFileSync4(appJsonPath, "utf-8");
2871
2943
  const parsed = JSON.parse(raw);
2872
2944
  if (!parsed.name || !Array.isArray(parsed.methods)) {
2873
2945
  return null;
@@ -2904,12 +2976,12 @@ function getWebInterfaceConfig(appConfig, cwd = process.cwd()) {
2904
2976
  if (!webInterface) {
2905
2977
  return null;
2906
2978
  }
2907
- const configPath = join6(cwd, webInterface.path);
2979
+ const configPath = join7(cwd, webInterface.path);
2908
2980
  if (!existsSync2(configPath)) {
2909
2981
  return null;
2910
2982
  }
2911
2983
  try {
2912
- const raw = readFileSync3(configPath, "utf-8");
2984
+ const raw = readFileSync4(configPath, "utf-8");
2913
2985
  const parsed = JSON.parse(raw);
2914
2986
  const web = parsed.web;
2915
2987
  if (!web || typeof web !== "object") {
@@ -2930,18 +3002,18 @@ function getWebProjectDir(appConfig, cwd = process.cwd()) {
2930
3002
  if (!webInterface) {
2931
3003
  return null;
2932
3004
  }
2933
- return dirname3(join6(cwd, webInterface.path));
3005
+ return dirname3(join7(cwd, webInterface.path));
2934
3006
  }
2935
3007
  function readTableSources(appConfig, cwd = process.cwd()) {
2936
3008
  const results = [];
2937
3009
  for (const table of appConfig.tables) {
2938
- const filePath = join6(cwd, table.path);
3010
+ const filePath = join7(cwd, table.path);
2939
3011
  if (!existsSync2(filePath)) {
2940
3012
  log.warn("config", "Table source file not found", { table: table.export, path: table.path });
2941
3013
  continue;
2942
3014
  }
2943
3015
  try {
2944
- const source = readFileSync3(filePath, "utf-8");
3016
+ const source = readFileSync4(filePath, "utf-8");
2945
3017
  const name = table.export;
2946
3018
  results.push({ name, source });
2947
3019
  } catch (err) {
@@ -2959,9 +3031,9 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
2959
3031
  const firstMethodPath = appConfig.methods[0].path;
2960
3032
  const parts = firstMethodPath.split("/");
2961
3033
  for (let i = parts.length - 1; i >= 1; i--) {
2962
- const candidate = join6(cwd, ...parts.slice(0, i));
2963
- if (existsSync2(join6(candidate, "package.json"))) {
2964
- if (!existsSync2(join6(candidate, "node_modules"))) {
3034
+ const candidate = join7(cwd, ...parts.slice(0, i));
3035
+ if (existsSync2(join7(candidate, "package.json"))) {
3036
+ if (!existsSync2(join7(candidate, "node_modules"))) {
2965
3037
  dirs.push(candidate);
2966
3038
  }
2967
3039
  break;
@@ -2969,8 +3041,8 @@ function findDirsNeedingInstall(appConfig, cwd = process.cwd()) {
2969
3041
  }
2970
3042
  }
2971
3043
  const webProjectDir = getWebProjectDir(appConfig, cwd);
2972
- if (webProjectDir && existsSync2(join6(webProjectDir, "package.json"))) {
2973
- if (!existsSync2(join6(webProjectDir, "node_modules"))) {
3044
+ if (webProjectDir && existsSync2(join7(webProjectDir, "package.json"))) {
3045
+ if (!existsSync2(join7(webProjectDir, "node_modules"))) {
2974
3046
  dirs.push(webProjectDir);
2975
3047
  }
2976
3048
  }
@@ -2999,11 +3071,11 @@ function detectGitBranch() {
2999
3071
 
3000
3072
  // src/dev/config/table-watcher.ts
3001
3073
  import { watch } from "chokidar";
3002
- import { join as join7, dirname as dirname4, basename as basename2 } from "path";
3074
+ import { join as join8, dirname as dirname4, basename as basename2 } from "path";
3003
3075
  function watchTableFiles(tables, cwd, onChanged) {
3004
3076
  if (tables.length === 0) return () => {
3005
3077
  };
3006
- const filePaths = tables.map((t) => join7(cwd, t.path));
3078
+ const filePaths = tables.map((t) => join8(cwd, t.path));
3007
3079
  let syncTimer;
3008
3080
  const watcher = watch(filePaths, {
3009
3081
  ignoreInitial: true,
@@ -3016,7 +3088,7 @@ function watchTableFiles(tables, cwd, onChanged) {
3016
3088
  });
3017
3089
  const dirToFiles = /* @__PURE__ */ new Map();
3018
3090
  for (const table of tables) {
3019
- const absPath = join7(cwd, table.path);
3091
+ const absPath = join8(cwd, table.path);
3020
3092
  const dir = dirname4(absPath);
3021
3093
  const file = basename2(absPath);
3022
3094
  if (!dirToFiles.has(dir)) dirToFiles.set(dir, /* @__PURE__ */ new Set());
@@ -3034,9 +3106,9 @@ function watchTableFiles(tables, cwd, onChanged) {
3034
3106
 
3035
3107
  // src/dev/config/config-watcher.ts
3036
3108
  import { watch as watch2 } from "chokidar";
3037
- import { join as join8 } from "path";
3109
+ import { join as join9 } from "path";
3038
3110
  function watchConfigFile(cwd, onChanged) {
3039
- const configPath = join8(cwd, "mindstudio.json");
3111
+ const configPath = join9(cwd, "mindstudio.json");
3040
3112
  let debounceTimer;
3041
3113
  const watcher = watch2(configPath, {
3042
3114
  ignoreInitial: true,
@@ -3105,4 +3177,4 @@ export {
3105
3177
  watchTableFiles,
3106
3178
  watchConfigFile
3107
3179
  };
3108
- //# sourceMappingURL=chunk-6LWKL7BC.js.map
3180
+ //# sourceMappingURL=chunk-XUR5YLIV.js.map