episoda 0.2.127 → 0.2.128

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.
@@ -2598,7 +2598,7 @@ var require_auth = __commonJS({
2598
2598
  Object.defineProperty(exports2, "__esModule", { value: true });
2599
2599
  exports2.getConfigDir = getConfigDir8;
2600
2600
  exports2.getConfigPath = getConfigPath;
2601
- exports2.loadConfig = loadConfig8;
2601
+ exports2.loadConfig = loadConfig9;
2602
2602
  exports2.saveConfig = saveConfig2;
2603
2603
  exports2.validateToken = validateToken;
2604
2604
  var fs24 = __importStar(require("fs"));
@@ -2635,7 +2635,7 @@ var require_auth = __commonJS({
2635
2635
  }
2636
2636
  }
2637
2637
  }
2638
- async function loadConfig8(configPath) {
2638
+ async function loadConfig9(configPath) {
2639
2639
  const fullPath = getConfigPath(configPath);
2640
2640
  if (fs24.existsSync(fullPath)) {
2641
2641
  try {
@@ -2815,7 +2815,7 @@ var require_package = __commonJS({
2815
2815
  "package.json"(exports2, module2) {
2816
2816
  module2.exports = {
2817
2817
  name: "episoda",
2818
- version: "0.2.126",
2818
+ version: "0.2.127",
2819
2819
  description: "CLI tool for Episoda local development workflow orchestration",
2820
2820
  main: "dist/index.js",
2821
2821
  types: "dist/index.d.ts",
@@ -3199,7 +3199,7 @@ var IPCServer = class {
3199
3199
  };
3200
3200
 
3201
3201
  // src/daemon/daemon-process.ts
3202
- var import_core13 = __toESM(require_dist());
3202
+ var import_core14 = __toESM(require_dist());
3203
3203
 
3204
3204
  // src/utils/update-checker.ts
3205
3205
  var import_child_process2 = require("child_process");
@@ -8659,6 +8659,7 @@ function generateClaudeConfig(options = {}) {
8659
8659
  }
8660
8660
 
8661
8661
  // src/agent/agent-manager.ts
8662
+ var import_core11 = __toESM(require_dist());
8662
8663
  var instance3 = null;
8663
8664
  function getAgentManager() {
8664
8665
  if (!instance3) {
@@ -8692,6 +8693,139 @@ var AgentManager = class {
8692
8693
  releaseLock();
8693
8694
  }
8694
8695
  }
8696
+ readJsonFileIfExists(filePath) {
8697
+ if (!fs19.existsSync(filePath)) return null;
8698
+ try {
8699
+ const content = fs19.readFileSync(filePath, "utf8");
8700
+ return JSON.parse(content);
8701
+ } catch (error) {
8702
+ console.warn(`[AgentManager] Failed to parse JSON at ${filePath}:`, error instanceof Error ? error.message : error);
8703
+ return null;
8704
+ }
8705
+ }
8706
+ readCodexTokensFromPath(filePath) {
8707
+ const data = this.readJsonFileIfExists(filePath);
8708
+ const tokens = data?.tokens;
8709
+ if (!tokens) return null;
8710
+ const accessToken = tokens.access_token;
8711
+ if (!accessToken) return null;
8712
+ return {
8713
+ accessToken,
8714
+ refreshToken: tokens.refresh_token,
8715
+ idToken: tokens.id_token,
8716
+ accountId: tokens.account_id
8717
+ };
8718
+ }
8719
+ readClaudeTokensFromPath(filePath) {
8720
+ const data = this.readJsonFileIfExists(filePath);
8721
+ const oauth = data?.claudeAiOauth;
8722
+ if (!oauth) return null;
8723
+ const accessToken = oauth.accessToken;
8724
+ if (!accessToken) return null;
8725
+ return {
8726
+ accessToken,
8727
+ refreshToken: oauth.refreshToken,
8728
+ expiresAt: typeof oauth.expiresAt === "number" ? oauth.expiresAt : void 0,
8729
+ scopes: Array.isArray(oauth.scopes) ? oauth.scopes : void 0
8730
+ };
8731
+ }
8732
+ loadProviderTokens(provider, sessionDir, legacyDir) {
8733
+ if (provider === "codex") {
8734
+ const sessionAuthPath = path20.join(sessionDir, "auth.json");
8735
+ const legacyAuthPath = path20.join(legacyDir, "auth.json");
8736
+ return this.readCodexTokensFromPath(sessionAuthPath) || this.readCodexTokensFromPath(legacyAuthPath);
8737
+ }
8738
+ const sessionCredentialsPath = path20.join(sessionDir, ".credentials.json");
8739
+ const legacyCredentialsPath = path20.join(legacyDir, ".credentials.json");
8740
+ return this.readClaudeTokensFromPath(sessionCredentialsPath) || this.readClaudeTokensFromPath(legacyCredentialsPath);
8741
+ }
8742
+ applyTokensToSession(session, tokens) {
8743
+ const currentAccess = session.credentials.oauthToken;
8744
+ const currentRefresh = session.credentials.refreshToken;
8745
+ const currentId = session.credentials.idToken;
8746
+ const currentAccount = session.credentials.accountId;
8747
+ const accessChanged = tokens.accessToken && tokens.accessToken !== currentAccess;
8748
+ const refreshChanged = tokens.refreshToken && tokens.refreshToken !== currentRefresh;
8749
+ const idChanged = tokens.idToken && tokens.idToken !== currentId;
8750
+ const accountChanged = tokens.accountId && tokens.accountId !== currentAccount;
8751
+ if (!accessChanged && !refreshChanged && !idChanged && !accountChanged) {
8752
+ return false;
8753
+ }
8754
+ if (tokens.accessToken) {
8755
+ session.credentials.oauthToken = tokens.accessToken;
8756
+ }
8757
+ if (tokens.refreshToken) {
8758
+ session.credentials.refreshToken = tokens.refreshToken;
8759
+ }
8760
+ if (tokens.idToken) {
8761
+ session.credentials.idToken = tokens.idToken;
8762
+ }
8763
+ if (tokens.accountId) {
8764
+ session.credentials.accountId = tokens.accountId;
8765
+ }
8766
+ if (tokens.expiresAt) {
8767
+ session.credentials.expiresAt = tokens.expiresAt;
8768
+ }
8769
+ if (tokens.scopes) {
8770
+ session.credentials.scopes = tokens.scopes;
8771
+ }
8772
+ return true;
8773
+ }
8774
+ async getApiAuth() {
8775
+ const envToken = process.env.EPISODA_ACCESS_TOKEN || process.env.EPISODA_SESSION_TOKEN;
8776
+ const envApiUrl = process.env.EPISODA_API_URL;
8777
+ if (envToken) {
8778
+ return { apiUrl: envApiUrl || "https://episoda.dev", token: envToken };
8779
+ }
8780
+ try {
8781
+ const config = await (0, import_core11.loadConfig)();
8782
+ if (!config?.access_token) {
8783
+ return null;
8784
+ }
8785
+ return {
8786
+ apiUrl: config.api_url || "https://episoda.dev",
8787
+ token: config.access_token
8788
+ };
8789
+ } catch (error) {
8790
+ console.warn("[AgentManager] Failed to load Episoda config for credential sync:", error instanceof Error ? error.message : error);
8791
+ return null;
8792
+ }
8793
+ }
8794
+ async syncTokensToVault(provider, tokens, source) {
8795
+ const auth = await this.getApiAuth();
8796
+ if (!auth) {
8797
+ console.warn("[AgentManager] Skipping credential sync - no Episoda auth token available");
8798
+ return;
8799
+ }
8800
+ try {
8801
+ const payload = {
8802
+ provider,
8803
+ access_token: tokens.accessToken,
8804
+ refresh_token: tokens.refreshToken,
8805
+ id_token: tokens.idToken,
8806
+ account_id: tokens.accountId,
8807
+ expires_at: tokens.expiresAt ? new Date(tokens.expiresAt).toISOString() : void 0,
8808
+ scopes: tokens.scopes,
8809
+ source
8810
+ };
8811
+ const response = await fetch(`${auth.apiUrl}/api/user/credentials/sync`, {
8812
+ method: "POST",
8813
+ headers: {
8814
+ "Authorization": `Bearer ${auth.token}`,
8815
+ "Content-Type": "application/json"
8816
+ },
8817
+ body: JSON.stringify(payload)
8818
+ });
8819
+ if (!response.ok) {
8820
+ const errorText = await response.text();
8821
+ console.warn(`[AgentManager] Credential sync failed (${provider}): ${response.status} ${errorText}`);
8822
+ return;
8823
+ }
8824
+ console.log(`[AgentManager] Synced ${provider} credentials to vault (${source})`);
8825
+ } catch (error) {
8826
+ console.warn("[AgentManager] Credential sync error:", error instanceof Error ? error.message : error);
8827
+ }
8828
+ }
8695
8829
  /**
8696
8830
  * EP1233: Get list of MCP servers to register for a session
8697
8831
  * EP1251: Added GitHub MCP for consolidated MCP configuration
@@ -9114,6 +9248,19 @@ If changes are needed, explain what needs to be done.`;
9114
9248
  const useApiKey = !useOAuth && !!session.credentials.apiKey;
9115
9249
  const sessionCodexDir = path20.join(os6.homedir(), ".codex", "sessions", sessionId);
9116
9250
  const sessionClaudeDir = path20.join(os6.homedir(), ".claude", "sessions", sessionId);
9251
+ const legacyCodexDir = path20.join(os6.homedir(), ".codex");
9252
+ const legacyClaudeDir = path20.join(os6.homedir(), ".claude");
9253
+ const sessionConfigDir = provider === "codex" ? sessionCodexDir : sessionClaudeDir;
9254
+ const legacyConfigDir = provider === "codex" ? legacyCodexDir : legacyClaudeDir;
9255
+ if (useOAuth) {
9256
+ const existingTokens = this.loadProviderTokens(provider, sessionConfigDir, legacyConfigDir);
9257
+ if (existingTokens) {
9258
+ const updated = this.applyTokensToSession(session, existingTokens);
9259
+ if (updated) {
9260
+ await this.syncTokensToVault(provider, existingTokens, "preflight");
9261
+ }
9262
+ }
9263
+ }
9117
9264
  if (provider === "codex") {
9118
9265
  await this.withConfigLock(async () => {
9119
9266
  fs19.mkdirSync(sessionCodexDir, { recursive: true });
@@ -9374,6 +9521,17 @@ If changes are needed, explain what needs to be done.`;
9374
9521
  console.log(`[AgentManager] EP1191: ${provider} CLI exited for session ${sessionId}: code=${code}, signal=${signal}, duration=${duration}ms, stdoutEvents=${stdoutEventCount}, chunksSent=${chunksSent}`);
9375
9522
  this.processes.delete(sessionId);
9376
9523
  this.removePidFile(sessionId);
9524
+ if (useOAuth) {
9525
+ void (async () => {
9526
+ const updatedTokens = this.loadProviderTokens(provider, sessionConfigDir, legacyConfigDir);
9527
+ if (updatedTokens) {
9528
+ const updated = this.applyTokensToSession(session, updatedTokens);
9529
+ if (updated) {
9530
+ await this.syncTokensToVault(provider, updatedTokens, "post-exit");
9531
+ }
9532
+ }
9533
+ })();
9534
+ }
9377
9535
  if (code === 0) {
9378
9536
  session.status = "stopped";
9379
9537
  onComplete(extractedSessionId || session.agentSessionId || session.claudeSessionId);
@@ -9798,7 +9956,7 @@ var AgentCommandQueue = class {
9798
9956
 
9799
9957
  // src/utils/dev-server.ts
9800
9958
  var import_child_process13 = require("child_process");
9801
- var import_core11 = __toESM(require_dist());
9959
+ var import_core12 = __toESM(require_dist());
9802
9960
  var fs20 = __toESM(require("fs"));
9803
9961
  var path21 = __toESM(require("path"));
9804
9962
  var MAX_RESTART_ATTEMPTS = 5;
@@ -9808,7 +9966,7 @@ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
9808
9966
  var NODE_MEMORY_LIMIT_MB = 2048;
9809
9967
  var activeServers = /* @__PURE__ */ new Map();
9810
9968
  function getLogsDir() {
9811
- const logsDir = path21.join((0, import_core11.getConfigDir)(), "logs");
9969
+ const logsDir = path21.join((0, import_core12.getConfigDir)(), "logs");
9812
9970
  if (!fs20.existsSync(logsDir)) {
9813
9971
  fs20.mkdirSync(logsDir, { recursive: true });
9814
9972
  }
@@ -10009,7 +10167,7 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
10009
10167
  console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`);
10010
10168
  let injectedEnvVars = {};
10011
10169
  try {
10012
- const config = await (0, import_core11.loadConfig)();
10170
+ const config = await (0, import_core12.loadConfig)();
10013
10171
  if (config?.access_token && config?.project_id) {
10014
10172
  const apiUrl = config.api_url || "https://episoda.dev";
10015
10173
  const result = await fetchEnvVarsWithCache(apiUrl, config.access_token, {
@@ -10129,7 +10287,7 @@ function getDevServerStatus() {
10129
10287
  var path22 = __toESM(require("path"));
10130
10288
  var fs21 = __toESM(require("fs"));
10131
10289
  var os7 = __toESM(require("os"));
10132
- var import_core12 = __toESM(require_dist());
10290
+ var import_core13 = __toESM(require_dist());
10133
10291
  function getEpisodaRoot2() {
10134
10292
  if (process.env.EPISODA_ROOT) {
10135
10293
  return process.env.EPISODA_ROOT;
@@ -10149,7 +10307,7 @@ function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
10149
10307
  };
10150
10308
  }
10151
10309
  async function getWorktreeInfoForModule(moduleUid) {
10152
- const config = await (0, import_core12.loadConfig)();
10310
+ const config = await (0, import_core13.loadConfig)();
10153
10311
  if (config?.workspace_slug && config?.project_slug) {
10154
10312
  return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug);
10155
10313
  }
@@ -10331,7 +10489,7 @@ async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
10331
10489
  refresh_token: tokenResponse.refresh_token || config.refresh_token,
10332
10490
  expires_at: now + tokenResponse.expires_in * 1e3
10333
10491
  };
10334
- await (0, import_core13.saveConfig)(updatedConfig);
10492
+ await (0, import_core14.saveConfig)(updatedConfig);
10335
10493
  console.log("[Daemon] EP904: Access token refreshed successfully");
10336
10494
  return updatedConfig;
10337
10495
  } catch (error) {
@@ -10340,7 +10498,7 @@ async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
10340
10498
  }
10341
10499
  }
10342
10500
  async function fetchWithAuth(url, options = {}, retryOnUnauthorized = true) {
10343
- let config = await (0, import_core13.loadConfig)();
10501
+ let config = await (0, import_core14.loadConfig)();
10344
10502
  if (!config?.access_token) {
10345
10503
  throw new Error("No access token configured");
10346
10504
  }
@@ -10367,7 +10525,7 @@ async function fetchWithAuth(url, options = {}, retryOnUnauthorized = true) {
10367
10525
  }
10368
10526
  async function fetchEnvVars2() {
10369
10527
  try {
10370
- const config = await (0, import_core13.loadConfig)();
10528
+ const config = await (0, import_core14.loadConfig)();
10371
10529
  if (!config?.project_id) {
10372
10530
  console.warn("[Daemon] EP973: No project_id in config, cannot fetch env vars");
10373
10531
  return {};
@@ -10478,7 +10636,7 @@ var Daemon = class _Daemon {
10478
10636
  console.log("[Daemon] Starting Episoda daemon...");
10479
10637
  this.machineId = await getMachineId();
10480
10638
  console.log(`[Daemon] Machine ID: ${this.machineId}`);
10481
- const config = await (0, import_core13.loadConfig)();
10639
+ const config = await (0, import_core14.loadConfig)();
10482
10640
  if (config?.machine_uuid || config?.device_id) {
10483
10641
  this.machineUuid = config.machine_uuid || config.device_id || null;
10484
10642
  console.log(`[Daemon] Loaded cached Machine UUID: ${this.machineUuid}`);
@@ -10729,7 +10887,7 @@ var Daemon = class _Daemon {
10729
10887
  };
10730
10888
  });
10731
10889
  this.ipcServer.on("verify-server-connection", async () => {
10732
- const config = await (0, import_core13.loadConfig)();
10890
+ const config = await (0, import_core14.loadConfig)();
10733
10891
  if (!config?.access_token || !config?.api_url) {
10734
10892
  return {
10735
10893
  verified: false,
@@ -10918,7 +11076,7 @@ var Daemon = class _Daemon {
10918
11076
  console.warn(`[Daemon] Stale connection detected for ${projectPath}, forcing reconnection`);
10919
11077
  await this.disconnectProject(projectPath);
10920
11078
  }
10921
- const config = await (0, import_core13.loadConfig)();
11079
+ const config = await (0, import_core14.loadConfig)();
10922
11080
  if (!config || !config.access_token) {
10923
11081
  throw new Error("No access token found. Please run: episoda auth");
10924
11082
  }
@@ -10939,8 +11097,8 @@ var Daemon = class _Daemon {
10939
11097
  wsUrl = `${wsProtocol}//${wsHostname}:${wsPort}`;
10940
11098
  }
10941
11099
  console.log(`[Daemon] Connecting to ${wsUrl} for project ${projectId}...`);
10942
- const client = new import_core13.EpisodaClient();
10943
- const gitExecutor = new import_core13.GitExecutor();
11100
+ const client = new import_core14.EpisodaClient();
11101
+ const gitExecutor = new import_core14.GitExecutor();
10944
11102
  const connection = {
10945
11103
  projectId,
10946
11104
  projectPath,
@@ -11142,7 +11300,7 @@ var Daemon = class _Daemon {
11142
11300
  port = allocatePort(cmd.moduleUid);
11143
11301
  }
11144
11302
  console.log(`[Daemon] EP1115: Using port ${port} for ${cmd.moduleUid} (mode: ${modeConfig.mode})`);
11145
- const devConfig = await (0, import_core13.loadConfig)();
11303
+ const devConfig = await (0, import_core14.loadConfig)();
11146
11304
  const customCommand = devConfig?.project_settings?.worktree_dev_server_script;
11147
11305
  const startResult = await previewManager.startPreview({
11148
11306
  moduleUid: cmd.moduleUid,
@@ -11833,7 +11991,7 @@ var Daemon = class _Daemon {
11833
11991
  */
11834
11992
  async cacheMachineUuid(machineUuid) {
11835
11993
  try {
11836
- const config = await (0, import_core13.loadConfig)();
11994
+ const config = await (0, import_core14.loadConfig)();
11837
11995
  if (!config) {
11838
11996
  console.warn("[Daemon] Cannot cache machine UUID - no config found");
11839
11997
  return;
@@ -11849,7 +12007,7 @@ var Daemon = class _Daemon {
11849
12007
  // Backward compatibility
11850
12008
  machine_id: this.machineId
11851
12009
  };
11852
- await (0, import_core13.saveConfig)(updatedConfig);
12010
+ await (0, import_core14.saveConfig)(updatedConfig);
11853
12011
  console.log(`[Daemon] Cached machine UUID to config: ${machineUuid}`);
11854
12012
  } catch (error) {
11855
12013
  console.warn("[Daemon] Failed to cache machine UUID:", error instanceof Error ? error.message : error);
@@ -11863,7 +12021,7 @@ var Daemon = class _Daemon {
11863
12021
  */
11864
12022
  async syncProjectSettings(projectId) {
11865
12023
  try {
11866
- const config = await (0, import_core13.loadConfig)();
12024
+ const config = await (0, import_core14.loadConfig)();
11867
12025
  if (!config) return;
11868
12026
  const apiUrl = config.api_url || "https://episoda.dev";
11869
12027
  const response = await fetchWithAuth(`${apiUrl}/api/projects/${projectId}/settings`);
@@ -11897,7 +12055,7 @@ var Daemon = class _Daemon {
11897
12055
  cached_at: Date.now()
11898
12056
  }
11899
12057
  };
11900
- await (0, import_core13.saveConfig)(updatedConfig);
12058
+ await (0, import_core14.saveConfig)(updatedConfig);
11901
12059
  console.log(`[Daemon] EP973: Project settings synced (slugs: ${projectSlug}/${workspaceSlug})`);
11902
12060
  }
11903
12061
  } catch (error) {
@@ -11917,7 +12075,7 @@ var Daemon = class _Daemon {
11917
12075
  console.warn("[Daemon] EP995: Cannot sync project path - machineUuid not available");
11918
12076
  return;
11919
12077
  }
11920
- const config = await (0, import_core13.loadConfig)();
12078
+ const config = await (0, import_core14.loadConfig)();
11921
12079
  if (!config) return;
11922
12080
  const apiUrl = config.api_url || "https://episoda.dev";
11923
12081
  const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.machineUuid}`, {
@@ -11950,7 +12108,7 @@ var Daemon = class _Daemon {
11950
12108
  */
11951
12109
  async updateModuleWorktreeStatus(moduleUid, status, worktreePath, errorMessage) {
11952
12110
  try {
11953
- const config = await (0, import_core13.loadConfig)();
12111
+ const config = await (0, import_core14.loadConfig)();
11954
12112
  if (!config) return;
11955
12113
  const apiUrl = config.api_url || "https://episoda.dev";
11956
12114
  const body = {
@@ -12005,7 +12163,7 @@ var Daemon = class _Daemon {
12005
12163
  console.log("[Daemon] EP1003: Cannot reconcile - machineUuid not available yet");
12006
12164
  return;
12007
12165
  }
12008
- const config = await (0, import_core13.loadConfig)();
12166
+ const config = await (0, import_core14.loadConfig)();
12009
12167
  if (!config) return;
12010
12168
  const apiUrl = config.api_url || "https://episoda.dev";
12011
12169
  const controller = new AbortController();
@@ -12206,7 +12364,7 @@ var Daemon = class _Daemon {
12206
12364
  try {
12207
12365
  const envVars = await fetchEnvVars2();
12208
12366
  console.log(`[Daemon] EP1002: Fetched ${Object.keys(envVars).length} env vars for ${moduleUid}`);
12209
- const config = await (0, import_core13.loadConfig)();
12367
+ const config = await (0, import_core14.loadConfig)();
12210
12368
  const setupConfig = config?.project_settings;
12211
12369
  await this.runWorktreeSetupSync(
12212
12370
  moduleUid,
@@ -12430,7 +12588,7 @@ var Daemon = class _Daemon {
12430
12588
  }
12431
12589
  this.healthCheckInProgress = true;
12432
12590
  try {
12433
- const config = await (0, import_core13.loadConfig)();
12591
+ const config = await (0, import_core14.loadConfig)();
12434
12592
  if (config?.access_token) {
12435
12593
  await this.performHealthChecks(config);
12436
12594
  }
@@ -12512,7 +12670,7 @@ var Daemon = class _Daemon {
12512
12670
  }
12513
12671
  console.log(`[Daemon] EP1042: Checking ${entries.length} registered dev server(s)...`);
12514
12672
  const activeModuleUids = [];
12515
- const config = await (0, import_core13.loadConfig)();
12673
+ const config = await (0, import_core14.loadConfig)();
12516
12674
  if (config?.access_token) {
12517
12675
  const apiUrl = config.api_url || "https://episoda.dev";
12518
12676
  try {
@@ -12648,7 +12806,7 @@ var Daemon = class _Daemon {
12648
12806
  */
12649
12807
  async fetchActiveModuleUids(projectId) {
12650
12808
  try {
12651
- const config = await (0, import_core13.loadConfig)();
12809
+ const config = await (0, import_core14.loadConfig)();
12652
12810
  if (!config?.access_token || !config?.api_url) {
12653
12811
  return null;
12654
12812
  }
@@ -12762,7 +12920,7 @@ var Daemon = class _Daemon {
12762
12920
  async restartTunnel(moduleUid, port) {
12763
12921
  const previewManager = getPreviewManager();
12764
12922
  try {
12765
- const config = await (0, import_core13.loadConfig)();
12923
+ const config = await (0, import_core14.loadConfig)();
12766
12924
  if (!config?.access_token) {
12767
12925
  console.error(`[Daemon] EP833: No access token for tunnel restart`);
12768
12926
  return;