@boxes-dev/dvb 0.2.45 → 0.2.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/dvb.cjs CHANGED
@@ -423,7 +423,7 @@ var init_repo = __esm({
423
423
  });
424
424
 
425
425
  // ../core/src/sprites.ts
426
- var import_node_crypto, import_node_util, import_ws, SpritesApiError, logger, USE_INTERNAL_SERVICE_API, INTERNAL_SERVICE_SIGNAL_TIMEOUT_SECONDS, toWebSocketErrorInfo, toWsUrl, shellQuote, requestJson, listSprites, requestNdjson, writeFile, normalizeServiceRecord, readFile, openExecSocket, attachExecSocket, openProxySocket, listExecSessions, listServices, internalServicesRequest, createCheckpointInternal, listServicesInternal, getServiceInternal, createServiceInternal, startServiceInternal, stopServiceInternal, execCommand, createSpritesClient;
426
+ var import_node_crypto, import_node_util, import_ws, SpritesApiError, logger, USE_INTERNAL_SERVICE_API, INTERNAL_SERVICE_SIGNAL_TIMEOUT_SECONDS, toWebSocketErrorInfo, toWsUrl, shellQuote, DEFAULT_SPRITES_HTTP_TIMEOUT_MS_IDEMPOTENT, DEFAULT_SPRITES_HTTP_TIMEOUT_MS_NON_IDEMPOTENT, DEFAULT_SPRITES_HTTP_MAX_ATTEMPTS, SPRITES_HTTP_RETRYABLE_STATUSES, delay, isIdempotentMethod, computeRetryDelayMs, isAbortError, getErrorCode, isRetryableFetchError, shouldRetrySpritesRequest, withSpritesRetry, requestJson, listSprites, requestNdjson, writeFile, normalizeServiceRecord, readFile, openExecSocket, attachExecSocket, openProxySocket, listExecSessions, listServices, internalServicesRequest, createCheckpointInternal, listServicesInternal, getServiceInternal, createServiceInternal, startServiceInternal, stopServiceInternal, execCommand, createSpritesClient;
427
427
  var init_sprites = __esm({
428
428
  "../core/src/sprites.ts"() {
429
429
  "use strict";
@@ -491,39 +491,126 @@ var init_sprites = __esm({
491
491
  return new URL(path26, `${protocol}//${base.host}`);
492
492
  };
493
493
  shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
494
- requestJson = async (apiBaseUrl, token, method, path26, body) => {
495
- const requestId = (0, import_node_crypto.randomUUID)();
496
- const url = new URL(path26, apiBaseUrl);
497
- const headers = {
498
- authorization: `Bearer ${token}`,
499
- "x-request-id": requestId
500
- };
501
- if (body) {
502
- headers["content-type"] = "application/json";
503
- }
504
- const init = { method, headers };
505
- if (body) {
506
- init.body = JSON.stringify(body);
494
+ DEFAULT_SPRITES_HTTP_TIMEOUT_MS_IDEMPOTENT = 5e3;
495
+ DEFAULT_SPRITES_HTTP_TIMEOUT_MS_NON_IDEMPOTENT = 2e4;
496
+ DEFAULT_SPRITES_HTTP_MAX_ATTEMPTS = 5;
497
+ SPRITES_HTTP_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
498
+ delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
499
+ isIdempotentMethod = (method) => {
500
+ const normalized = method.toUpperCase();
501
+ return normalized === "GET" || normalized === "HEAD" || normalized === "PUT" || normalized === "DELETE" || normalized === "OPTIONS";
502
+ };
503
+ computeRetryDelayMs = (attempt) => {
504
+ const base = 250;
505
+ const cap = 2500;
506
+ const exp = Math.min(cap, base * 2 ** Math.max(0, attempt - 1));
507
+ const jitter = Math.floor(Math.random() * 120);
508
+ return exp + jitter;
509
+ };
510
+ isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted"));
511
+ getErrorCode = (error) => {
512
+ if (!error || typeof error !== "object") return void 0;
513
+ const record = error;
514
+ const code2 = record.code;
515
+ return typeof code2 === "string" || typeof code2 === "number" ? String(code2) : void 0;
516
+ };
517
+ isRetryableFetchError = (error) => {
518
+ if (isAbortError(error)) return true;
519
+ if (error instanceof TypeError) return true;
520
+ const code2 = getErrorCode(error);
521
+ return code2 === "ECONNRESET" || code2 === "ETIMEDOUT" || code2 === "EAI_AGAIN" || code2 === "ENOTFOUND" || code2 === "ECONNREFUSED";
522
+ };
523
+ shouldRetrySpritesRequest = (method, error) => {
524
+ if (!isIdempotentMethod(method)) return false;
525
+ if (error instanceof SpritesApiError) {
526
+ return SPRITES_HTTP_RETRYABLE_STATUSES.has(error.status);
527
+ }
528
+ return isRetryableFetchError(error);
529
+ };
530
+ withSpritesRetry = async (options) => {
531
+ const opId = (0, import_node_crypto.randomUUID)();
532
+ const timeoutMs = options.timeoutMs ?? (isIdempotentMethod(options.method) ? DEFAULT_SPRITES_HTTP_TIMEOUT_MS_IDEMPOTENT : DEFAULT_SPRITES_HTTP_TIMEOUT_MS_NON_IDEMPOTENT);
533
+ const maxAttemptsRaw = options.maxAttempts ?? DEFAULT_SPRITES_HTTP_MAX_ATTEMPTS;
534
+ const maxAttempts = isIdempotentMethod(options.method) ? Math.max(1, maxAttemptsRaw) : 1;
535
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
536
+ const requestId = (0, import_node_crypto.randomUUID)();
537
+ const controller = new AbortController();
538
+ const timeoutHandle = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
539
+ try {
540
+ logger.info("sprites_request", {
541
+ opId,
542
+ attempt,
543
+ requestId,
544
+ method: options.method,
545
+ path: options.path,
546
+ timeoutMs
547
+ });
548
+ return await options.run(controller.signal, requestId);
549
+ } catch (error) {
550
+ const retryable = shouldRetrySpritesRequest(options.method, error);
551
+ const remaining = maxAttempts - attempt;
552
+ if (!retryable || remaining <= 0) {
553
+ throw error;
554
+ }
555
+ const delayMs = computeRetryDelayMs(attempt);
556
+ logger.warn("sprites_http_retry", {
557
+ opId,
558
+ attempt,
559
+ requestId,
560
+ method: options.method,
561
+ path: options.path,
562
+ timeoutMs,
563
+ delayMs,
564
+ remainingAttempts: remaining,
565
+ errorName: error instanceof Error ? error.name : void 0,
566
+ errorMessage: error instanceof Error ? error.message : String(error),
567
+ errorCode: getErrorCode(error),
568
+ ...error instanceof SpritesApiError ? { status: error.status } : {}
569
+ });
570
+ await delay(delayMs);
571
+ } finally {
572
+ if (timeoutHandle) clearTimeout(timeoutHandle);
573
+ }
507
574
  }
508
- logger.info("sprites_request", { requestId, method, path: path26 });
509
- const response = await fetch(url, init);
510
- const text = await response.text();
511
- logger.info("sprites_response", {
512
- requestId,
575
+ throw new Error(`Sprites request failed: ${options.method} ${options.path}`);
576
+ };
577
+ requestJson = async (apiBaseUrl, token, method, path26, body) => {
578
+ return await withSpritesRetry({
513
579
  method,
514
580
  path: path26,
515
- status: response.status
581
+ run: async (signal, requestId) => {
582
+ const url = new URL(path26, apiBaseUrl);
583
+ const headers = {
584
+ authorization: `Bearer ${token}`,
585
+ "x-request-id": requestId
586
+ };
587
+ if (body) {
588
+ headers["content-type"] = "application/json";
589
+ }
590
+ const init = { method, headers, signal };
591
+ if (body) {
592
+ init.body = JSON.stringify(body);
593
+ }
594
+ const response = await fetch(url, init);
595
+ const text = await response.text();
596
+ logger.info("sprites_response", {
597
+ requestId,
598
+ method,
599
+ path: path26,
600
+ status: response.status
601
+ });
602
+ if (!response.ok) {
603
+ const snippet = text.trim().slice(0, 200);
604
+ throw new SpritesApiError(response.status, method, path26, snippet);
605
+ }
606
+ if (!text) return null;
607
+ try {
608
+ return JSON.parse(text);
609
+ } catch {
610
+ return text;
611
+ }
612
+ }
516
613
  });
517
- if (!response.ok) {
518
- const snippet = text.trim().slice(0, 200);
519
- throw new SpritesApiError(response.status, method, path26, snippet);
520
- }
521
- if (!text) return null;
522
- try {
523
- return JSON.parse(text);
524
- } catch {
525
- return text;
526
- }
527
614
  };
528
615
  listSprites = async (apiBaseUrl, token, options) => {
529
616
  const params = new URLSearchParams();
@@ -564,88 +651,99 @@ var init_sprites = __esm({
564
651
  return response;
565
652
  };
566
653
  requestNdjson = async (apiBaseUrl, token, method, path26, body) => {
567
- const requestId = (0, import_node_crypto.randomUUID)();
568
- const url = new URL(path26, apiBaseUrl);
569
- const headers = {
570
- authorization: `Bearer ${token}`,
571
- "x-request-id": requestId
572
- };
573
- if (body) {
574
- headers["content-type"] = "application/json";
575
- }
576
- const init = { method, headers };
577
- if (body) {
578
- init.body = JSON.stringify(body);
579
- }
580
- logger.info("sprites_request", { requestId, method, path: path26 });
581
- const response = await fetch(url, init);
582
- logger.info("sprites_response", {
583
- requestId,
654
+ return await withSpritesRetry({
584
655
  method,
585
656
  path: path26,
586
- status: response.status
587
- });
588
- const text = await response.text();
589
- if (!response.ok) {
590
- const snippet = text.trim().slice(0, 200);
591
- throw new SpritesApiError(response.status, method, path26, snippet);
592
- }
593
- const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
594
- const events = [];
595
- for (const line of lines) {
596
- try {
597
- const parsed = JSON.parse(line);
598
- if (Array.isArray(parsed)) {
599
- for (const entry of parsed) {
600
- if (entry && typeof entry === "object") {
601
- events.push(entry);
602
- } else {
603
- events.push({ value: entry });
657
+ run: async (signal, requestId) => {
658
+ const url = new URL(path26, apiBaseUrl);
659
+ const headers = {
660
+ authorization: `Bearer ${token}`,
661
+ "x-request-id": requestId
662
+ };
663
+ if (body) {
664
+ headers["content-type"] = "application/json";
665
+ }
666
+ const init = { method, headers, signal };
667
+ if (body) {
668
+ init.body = JSON.stringify(body);
669
+ }
670
+ const response = await fetch(url, init);
671
+ logger.info("sprites_response", {
672
+ requestId,
673
+ method,
674
+ path: path26,
675
+ status: response.status
676
+ });
677
+ const text = await response.text();
678
+ if (!response.ok) {
679
+ const snippet = text.trim().slice(0, 200);
680
+ throw new SpritesApiError(response.status, method, path26, snippet);
681
+ }
682
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
683
+ const events = [];
684
+ for (const line of lines) {
685
+ try {
686
+ const parsed = JSON.parse(line);
687
+ if (Array.isArray(parsed)) {
688
+ for (const entry of parsed) {
689
+ if (entry && typeof entry === "object") {
690
+ events.push(entry);
691
+ } else {
692
+ events.push({ value: entry });
693
+ }
694
+ }
695
+ continue;
696
+ }
697
+ if (parsed && typeof parsed === "object") {
698
+ events.push(parsed);
699
+ continue;
604
700
  }
701
+ events.push({ value: parsed });
702
+ } catch {
703
+ events.push({ raw: line });
605
704
  }
606
- continue;
607
705
  }
608
- if (parsed && typeof parsed === "object") {
609
- events.push(parsed);
610
- continue;
611
- }
612
- events.push({ value: parsed });
613
- } catch {
614
- events.push({ raw: line });
706
+ return events;
615
707
  }
616
- }
617
- return events;
708
+ });
618
709
  };
619
710
  writeFile = async (apiBaseUrl, token, name, remotePath, data) => {
620
- const requestId = (0, import_node_crypto.randomUUID)();
621
711
  const path26 = `/v1/sprites/${name}/fs/write`;
622
712
  const url = new URL(path26, apiBaseUrl);
623
713
  url.searchParams.set("path", remotePath);
624
714
  url.searchParams.set("mkdir", "true");
625
- logger.info("sprites_fs_write", {
626
- requestId,
627
- path: path26,
628
- name,
629
- remotePath,
630
- size: data.length
631
- });
632
- const response = await fetch(url, {
715
+ await withSpritesRetry({
633
716
  method: "PUT",
634
- headers: {
635
- authorization: `Bearer ${token}`,
636
- "content-type": "application/octet-stream",
637
- "x-request-id": requestId
638
- },
639
- body: data
640
- });
641
- logger.info("sprites_fs_write_response", {
642
- requestId,
643
717
  path: path26,
644
- status: response.status
718
+ run: async (signal, requestId) => {
719
+ logger.info("sprites_fs_write", {
720
+ requestId,
721
+ path: path26,
722
+ name,
723
+ remotePath,
724
+ size: data.length
725
+ });
726
+ const response = await fetch(url, {
727
+ method: "PUT",
728
+ headers: {
729
+ authorization: `Bearer ${token}`,
730
+ "content-type": "application/octet-stream",
731
+ "x-request-id": requestId
732
+ },
733
+ body: data,
734
+ signal
735
+ });
736
+ logger.info("sprites_fs_write_response", {
737
+ requestId,
738
+ path: path26,
739
+ status: response.status
740
+ });
741
+ if (!response.ok) {
742
+ const snippet = (await response.text()).trim().slice(0, 200);
743
+ throw new SpritesApiError(response.status, "PUT", path26, snippet);
744
+ }
745
+ }
645
746
  });
646
- if (!response.ok) {
647
- throw new Error(`Sprites FS write failed: ${response.status}`);
648
- }
649
747
  };
650
748
  normalizeServiceRecord = (entry) => {
651
749
  const rawName = entry.name;
@@ -676,38 +774,44 @@ var init_sprites = __esm({
676
774
  return service;
677
775
  };
678
776
  readFile = async (apiBaseUrl, token, name, options) => {
679
- const requestId = (0, import_node_crypto.randomUUID)();
680
777
  const path26 = `/v1/sprites/${name}/fs/read`;
681
778
  const url = new URL(path26, apiBaseUrl);
682
779
  url.searchParams.set("path", options.path);
683
780
  if (options.workingDir) {
684
781
  url.searchParams.set("workingDir", options.workingDir);
685
782
  }
686
- logger.info("sprites_fs_read", {
687
- requestId,
688
- path: path26,
689
- name,
690
- remotePath: options.path,
691
- workingDir: options.workingDir
692
- });
693
- const response = await fetch(url, {
783
+ return await withSpritesRetry({
694
784
  method: "GET",
695
- headers: {
696
- authorization: `Bearer ${token}`,
697
- "x-request-id": requestId
698
- }
699
- });
700
- logger.info("sprites_fs_read_response", {
701
- requestId,
702
785
  path: path26,
703
- status: response.status
786
+ run: async (signal, requestId) => {
787
+ logger.info("sprites_fs_read", {
788
+ requestId,
789
+ path: path26,
790
+ name,
791
+ remotePath: options.path,
792
+ workingDir: options.workingDir
793
+ });
794
+ const response = await fetch(url, {
795
+ method: "GET",
796
+ headers: {
797
+ authorization: `Bearer ${token}`,
798
+ "x-request-id": requestId
799
+ },
800
+ signal
801
+ });
802
+ logger.info("sprites_fs_read_response", {
803
+ requestId,
804
+ path: path26,
805
+ status: response.status
806
+ });
807
+ if (!response.ok) {
808
+ const snippet = (await response.text()).trim().slice(0, 200);
809
+ throw new SpritesApiError(response.status, "GET", path26, snippet);
810
+ }
811
+ const buffer = await response.arrayBuffer();
812
+ return new Uint8Array(buffer);
813
+ }
704
814
  });
705
- if (!response.ok) {
706
- const snippet = (await response.text()).trim().slice(0, 200);
707
- throw new SpritesApiError(response.status, "GET", path26, snippet);
708
- }
709
- const buffer = await response.arrayBuffer();
710
- return new Uint8Array(buffer);
711
815
  };
712
816
  openExecSocket = (apiBaseUrl, token, name, options) => {
713
817
  const url = toWsUrl(apiBaseUrl, `/v1/sprites/${name}/exec`);
@@ -4748,27 +4852,27 @@ var init_authentication_manager = __esm({
4748
4852
  );
4749
4853
  return;
4750
4854
  }
4751
- let delay2 = Math.min(
4855
+ let delay3 = Math.min(
4752
4856
  MAXIMUM_REFRESH_DELAY,
4753
4857
  (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1e3
4754
4858
  );
4755
- if (delay2 <= 0) {
4859
+ if (delay3 <= 0) {
4756
4860
  this.logger.warn(
4757
4861
  `Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`
4758
4862
  );
4759
- delay2 = 0;
4863
+ delay3 = 0;
4760
4864
  }
4761
4865
  const refetchTokenTimeoutId = setTimeout(() => {
4762
4866
  this._logVerbose("running scheduled token refetch");
4763
4867
  void this.refetchToken();
4764
- }, delay2);
4868
+ }, delay3);
4765
4869
  this.setAuthState({
4766
4870
  state: "waitingForScheduledRefetch",
4767
4871
  refetchTokenTimeoutId,
4768
4872
  config: this.authState.config
4769
4873
  });
4770
4874
  this._logVerbose(
4771
- `scheduled preemptive auth token refetching in ${delay2}ms`
4875
+ `scheduled preemptive auth token refetching in ${delay3}ms`
4772
4876
  );
4773
4877
  }
4774
4878
  // Protects against simultaneous calls to `setConfig`
@@ -19763,8 +19867,8 @@ var init_connect = __esm({
19763
19867
  pendingPortLogs.set(event.port, token2);
19764
19868
  const delays = [30, 60, 120, 200];
19765
19869
  void (async () => {
19766
- for (const delay2 of delays) {
19767
- await new Promise((resolve) => setTimeout(resolve, delay2));
19870
+ for (const delay3 of delays) {
19871
+ await new Promise((resolve) => setTimeout(resolve, delay3));
19768
19872
  if (pendingPortLogs.get(event.port) !== token2) {
19769
19873
  return;
19770
19874
  }
@@ -22339,7 +22443,7 @@ var init_weztermMux = __esm({
22339
22443
  });
22340
22444
 
22341
22445
  // src/devbox/commands/init/remote.ts
22342
- var import_node_crypto10, DAEMON_DIR, DAEMON_TARBALL, DAEMON_BUNDLE_DIR, DAEMON_ENTRY, DAEMON_WRAPPER, DAEMON_SERVICE_NAME, DAEMON_CONFIG_FILE, DEFAULT_DAEMON_BASE_URL, DEFAULT_HEARTBEAT_MS, BOOTSTRAP_EXEC_TIMEOUT_MS, BOOTSTRAP_EXEC_RETRY_DELAY_MS, logger8, truncateTail, shellQuote3, expandHome2, execWithLog, isExecTimeoutError, delay, writeRemoteFile, bootstrapDevbox, buildWeztermMuxConfig, buildWeztermMuxRunner, patchBashrc, writeRemoteCodexConfig, stageRemoteSetupArtifacts, resolveDaemonUrl, fetchDaemonBinary, buildDaemonConfig, isSameArgs, ensureSpriteDaemonService, installWeztermMux, ensureWeztermMuxService, hasWeztermMuxBinary, isWeztermMuxHealthy, installSpriteDaemon;
22446
+ var import_node_crypto10, DAEMON_DIR, DAEMON_TARBALL, DAEMON_BUNDLE_DIR, DAEMON_ENTRY, DAEMON_WRAPPER, DAEMON_SERVICE_NAME, DAEMON_CONFIG_FILE, DEFAULT_DAEMON_BASE_URL, DEFAULT_HEARTBEAT_MS, BOOTSTRAP_EXEC_TIMEOUT_MS, logger8, truncateTail, shellQuote3, expandHome2, execWithLog, writeRemoteFile, bootstrapDevbox, buildWeztermMuxConfig, buildWeztermMuxRunner, patchBashrc, writeRemoteCodexConfig, stageRemoteSetupArtifacts, resolveDaemonUrl, fetchDaemonBinary, buildDaemonConfig, isSameArgs, ensureSpriteDaemonService, installWeztermMux, ensureWeztermMuxService, hasWeztermMuxBinary, isWeztermMuxHealthy, installSpriteDaemon;
22343
22447
  var init_remote = __esm({
22344
22448
  "src/devbox/commands/init/remote.ts"() {
22345
22449
  "use strict";
@@ -22358,7 +22462,6 @@ var init_remote = __esm({
22358
22462
  DEFAULT_DAEMON_BASE_URL = process.env.SPRITE_DAEMON_BASE_URL?.trim() || "https://deploy-dev.boxes.dev";
22359
22463
  DEFAULT_HEARTBEAT_MS = 3e4;
22360
22464
  BOOTSTRAP_EXEC_TIMEOUT_MS = 9e4;
22361
- BOOTSTRAP_EXEC_RETRY_DELAY_MS = 1e4;
22362
22465
  logger8 = createLogger("devbox-init");
22363
22466
  truncateTail = (value, maxChars) => {
22364
22467
  if (value.length <= maxChars) return value;
@@ -22406,8 +22509,6 @@ var init_remote = __esm({
22406
22509
  });
22407
22510
  return result;
22408
22511
  };
22409
- isExecTimeoutError = (error) => error instanceof Error && typeof error.message === "string" && error.message.startsWith("Exec websocket timed out after ");
22410
- delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
22411
22512
  writeRemoteFile = async (client, canonical, path26, content, stage) => {
22412
22513
  const requestId = (0, import_node_crypto10.randomUUID)();
22413
22514
  logger8.info("sprites_request", {
@@ -22439,28 +22540,13 @@ var init_remote = __esm({
22439
22540
  " sudo -n chown -R sprite:sprite /home/sprite/.devbox || true",
22440
22541
  "fi"
22441
22542
  ].join("\n");
22442
- let permsResult = await execWithLog(
22543
+ const permsResult = await execWithLog(
22443
22544
  client,
22444
22545
  canonical,
22445
22546
  permissionsScript,
22446
22547
  "bootstrap-permissions",
22447
22548
  { timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS }
22448
- ).catch(async (error) => {
22449
- if (!isExecTimeoutError(error)) throw error;
22450
- logger8.warn("sprites_exec_retry_after_timeout", {
22451
- stage: "bootstrap-permissions",
22452
- timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS,
22453
- delayMs: BOOTSTRAP_EXEC_RETRY_DELAY_MS
22454
- });
22455
- await delay(BOOTSTRAP_EXEC_RETRY_DELAY_MS);
22456
- return await execWithLog(
22457
- client,
22458
- canonical,
22459
- permissionsScript,
22460
- "bootstrap-permissions",
22461
- { timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS }
22462
- );
22463
- });
22549
+ );
22464
22550
  if (permsResult.exitCode !== 0) {
22465
22551
  const details = permsResult.stderr || permsResult.stdout || "";
22466
22552
  throw new Error(
@@ -23943,17 +24029,26 @@ var init_template = __esm({
23943
24029
  });
23944
24030
 
23945
24031
  // src/devbox/commands/init/codex/prompts.ts
23946
- var import_promises23, import_node_path17, resolvePromptsDir, readPromptTemplate, loadPrompt, loadLocalEnvSecretsScanPrompt, loadLocalExternalScanPrompt, loadLocalExtraArtifactsScanPrompt, loadLocalServicesScanPrompt;
24032
+ var import_node_fs5, import_promises23, import_node_path17, resolvePromptsDir, readPromptTemplate, loadPrompt, loadLocalEnvSecretsScanPrompt, loadLocalExternalScanPrompt, loadLocalExtraArtifactsScanPrompt, loadLocalServicesScanPrompt;
23947
24033
  var init_prompts = __esm({
23948
24034
  "src/devbox/commands/init/codex/prompts.ts"() {
23949
24035
  "use strict";
24036
+ import_node_fs5 = __toESM(require("node:fs"), 1);
23950
24037
  import_promises23 = __toESM(require("node:fs/promises"), 1);
23951
24038
  import_node_path17 = __toESM(require("node:path"), 1);
23952
24039
  init_template();
23953
24040
  resolvePromptsDir = () => {
23954
- const binPath = process.argv[1];
23955
- if (binPath) {
23956
- return import_node_path17.default.resolve(import_node_path17.default.dirname(binPath), "..", "prompts");
24041
+ const argv1 = typeof process.argv[1] === "string" ? process.argv[1] : "";
24042
+ if (argv1) {
24043
+ try {
24044
+ const real = import_node_fs5.default.realpathSync(argv1);
24045
+ return import_node_path17.default.resolve(import_node_path17.default.dirname(real), "..", "prompts");
24046
+ } catch {
24047
+ }
24048
+ try {
24049
+ return import_node_path17.default.resolve(import_node_path17.default.dirname(argv1), "..", "prompts");
24050
+ } catch {
24051
+ }
23957
24052
  }
23958
24053
  return import_node_path17.default.resolve(process.cwd(), "prompts");
23959
24054
  };
@@ -24475,18 +24570,18 @@ fi`
24475
24570
  });
24476
24571
 
24477
24572
  // src/devbox/commands/init/codex/plan.ts
24478
- var import_node_path18, import_node_fs5, import_promises24, resolveSchemaPath, writeSetupEnvSecretsSchema, writeSetupExternalSchema, writeSetupExtraArtifactsSchema, writeServicesSchema, isObject4, isServicesSelection, isSetupPlan, isSetupEnvSecretsPlan, isSetupExternalPlan, isSetupExtraArtifactsPlan, isServicesPlan, readSetupPlan, readSetupEnvSecretsPlan, readSetupExternalPlan, readSetupExtraArtifactsPlan, readServicesPlan, writeSetupPlan, mergeSetupScans;
24573
+ var import_node_path18, import_node_fs6, import_promises24, resolveSchemaPath, writeSetupEnvSecretsSchema, writeSetupExternalSchema, writeSetupExtraArtifactsSchema, writeServicesSchema, isObject4, isServicesSelection, isSetupPlan, isSetupEnvSecretsPlan, isSetupExternalPlan, isSetupExtraArtifactsPlan, isServicesPlan, readSetupPlan, readSetupEnvSecretsPlan, readSetupExternalPlan, readSetupExtraArtifactsPlan, readServicesPlan, writeSetupPlan, mergeSetupScans;
24479
24574
  var init_plan = __esm({
24480
24575
  "src/devbox/commands/init/codex/plan.ts"() {
24481
24576
  "use strict";
24482
24577
  import_node_path18 = __toESM(require("node:path"), 1);
24483
- import_node_fs5 = __toESM(require("node:fs"), 1);
24578
+ import_node_fs6 = __toESM(require("node:fs"), 1);
24484
24579
  import_promises24 = __toESM(require("node:fs/promises"), 1);
24485
24580
  resolveSchemaPath = (fileName) => {
24486
24581
  const argv1 = typeof process.argv[1] === "string" ? process.argv[1] : "";
24487
24582
  if (argv1) {
24488
24583
  try {
24489
- const real = import_node_fs5.default.realpathSync(argv1);
24584
+ const real = import_node_fs6.default.realpathSync(argv1);
24490
24585
  return import_node_path18.default.resolve(import_node_path18.default.dirname(real), "..", "codex", fileName);
24491
24586
  } catch {
24492
24587
  }
@@ -24783,12 +24878,12 @@ ${bottom}
24783
24878
  });
24784
24879
 
24785
24880
  // src/devbox/commands/init/codex/local.ts
24786
- var import_node_child_process9, import_node_fs6, import_promises25, import_node_path19, stripAnsi3, extractBoldText2, runCodexExec, runLocalSetupEnvSecretsScan, runLocalSetupExternalScan, runLocalSetupExtraArtifactsScan, runLocalServicesScan, toPosixPath, toRepoRelativePath, countSecretVars, buildEnvFileHint, buildExternalDependencyLabel, promptForPlanApproval, promptForServicesApproval;
24881
+ var import_node_child_process9, import_node_fs7, import_promises25, import_node_path19, stripAnsi3, extractBoldText2, runCodexExec, runLocalSetupEnvSecretsScan, runLocalSetupExternalScan, runLocalSetupExtraArtifactsScan, runLocalServicesScan, toPosixPath, toRepoRelativePath, countSecretVars, buildEnvFileHint, buildExternalDependencyLabel, promptForPlanApproval, promptForServicesApproval;
24787
24882
  var init_local = __esm({
24788
24883
  "src/devbox/commands/init/codex/local.ts"() {
24789
24884
  "use strict";
24790
24885
  import_node_child_process9 = require("node:child_process");
24791
- import_node_fs6 = require("node:fs");
24886
+ import_node_fs7 = require("node:fs");
24792
24887
  import_promises25 = __toESM(require("node:fs/promises"), 1);
24793
24888
  import_node_path19 = __toESM(require("node:path"), 1);
24794
24889
  init_dist2();
@@ -24807,11 +24902,11 @@ var init_local = __esm({
24807
24902
  try {
24808
24903
  if (stdoutLogPath) {
24809
24904
  await import_promises25.default.mkdir(import_node_path19.default.dirname(stdoutLogPath), { recursive: true });
24810
- stdoutStream = (0, import_node_fs6.createWriteStream)(stdoutLogPath, { flags: "a" });
24905
+ stdoutStream = (0, import_node_fs7.createWriteStream)(stdoutLogPath, { flags: "a" });
24811
24906
  }
24812
24907
  if (stderrLogPath) {
24813
24908
  await import_promises25.default.mkdir(import_node_path19.default.dirname(stderrLogPath), { recursive: true });
24814
- stderrStream = (0, import_node_fs6.createWriteStream)(stderrLogPath, { flags: "a" });
24909
+ stderrStream = (0, import_node_fs7.createWriteStream)(stderrLogPath, { flags: "a" });
24815
24910
  }
24816
24911
  } catch {
24817
24912
  stdoutStream = null;
@@ -26746,7 +26841,7 @@ var init_progress = __esm({
26746
26841
  });
26747
26842
 
26748
26843
  // src/devbox/commands/init/index.ts
26749
- var import_node_path22, import_promises28, import_node_os9, resolveInitStatus, mergeLocalPaths, ensurePrivateDir, migrateLegacyRepoDevboxDir, buildServicesTomlUpdates, extractCheckpointId, writeRemoteServicesToml, enableRemoteServices, throwInitCanceled, promptBeforeOpenBrowser, toPosixPath2, toRepoRelativePath2, isOutsideRepoPath, ensureWorkdirOwnership, ensureGitSafeDirectory, runInit;
26844
+ var import_node_path22, import_promises28, import_node_os9, resolveInitStatus, mergeLocalPaths, ensurePrivateDir, DEFAULT_INIT_STEP_RETRIES, INIT_STEP_RETRYABLE_STATUSES, delay2, computeRetryDelayMs2, getErrorCode2, parseExecUnexpectedResponseStatus, isRetryableInitError, retryInitStep, migrateLegacyRepoDevboxDir, buildServicesTomlUpdates, extractCheckpointId, writeRemoteServicesToml, enableRemoteServices, throwInitCanceled, promptBeforeOpenBrowser, toPosixPath2, toRepoRelativePath2, isOutsideRepoPath, ensureWorkdirOwnership, ensureGitSafeDirectory, runInit;
26750
26845
  var init_init = __esm({
26751
26846
  "src/devbox/commands/init/index.ts"() {
26752
26847
  "use strict";
@@ -26791,6 +26886,88 @@ var init_init = __esm({
26791
26886
  } catch {
26792
26887
  }
26793
26888
  };
26889
+ DEFAULT_INIT_STEP_RETRIES = 3;
26890
+ INIT_STEP_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
26891
+ 408,
26892
+ 409,
26893
+ 425,
26894
+ 429,
26895
+ 500,
26896
+ 502,
26897
+ 503,
26898
+ 504
26899
+ ]);
26900
+ delay2 = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26901
+ computeRetryDelayMs2 = (retryIndex) => {
26902
+ const base = 500;
26903
+ const cap = 5e3;
26904
+ const exp = Math.min(cap, base * 2 ** Math.max(0, retryIndex - 1));
26905
+ const jitter = Math.floor(Math.random() * 150);
26906
+ return exp + jitter;
26907
+ };
26908
+ getErrorCode2 = (error) => {
26909
+ if (!error || typeof error !== "object") return void 0;
26910
+ const record = error;
26911
+ const code2 = record.code;
26912
+ return typeof code2 === "string" || typeof code2 === "number" ? String(code2) : void 0;
26913
+ };
26914
+ parseExecUnexpectedResponseStatus = (error) => {
26915
+ if (!(error instanceof Error)) return null;
26916
+ const match = /Unexpected server response: (\\d{3})/.exec(error.message);
26917
+ if (!match) return null;
26918
+ const status = Number(match[1]);
26919
+ return Number.isFinite(status) ? status : null;
26920
+ };
26921
+ isRetryableInitError = (error) => {
26922
+ if (error instanceof SpritesApiError) {
26923
+ return INIT_STEP_RETRYABLE_STATUSES.has(error.status);
26924
+ }
26925
+ if (error instanceof TypeError) return true;
26926
+ if (error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted"))) {
26927
+ return true;
26928
+ }
26929
+ const execStatus = parseExecUnexpectedResponseStatus(error);
26930
+ if (execStatus !== null) {
26931
+ return INIT_STEP_RETRYABLE_STATUSES.has(execStatus);
26932
+ }
26933
+ const code2 = getErrorCode2(error);
26934
+ return code2 === "ECONNRESET" || code2 === "ETIMEDOUT" || code2 === "EAI_AGAIN" || code2 === "ENOTFOUND" || code2 === "ECONNREFUSED";
26935
+ };
26936
+ retryInitStep = async ({
26937
+ status,
26938
+ title,
26939
+ retries = DEFAULT_INIT_STEP_RETRIES,
26940
+ shouldRetry = isRetryableInitError,
26941
+ fn
26942
+ }) => {
26943
+ let lastError = null;
26944
+ const maxRetries = Math.max(0, Math.floor(retries));
26945
+ const attempts = maxRetries + 1;
26946
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
26947
+ try {
26948
+ return await fn();
26949
+ } catch (error) {
26950
+ lastError = error;
26951
+ const retryIndex = attempt;
26952
+ if (retryIndex > maxRetries || !shouldRetry(error)) {
26953
+ throw error;
26954
+ }
26955
+ status.stage(`${title} (retry ${retryIndex}/${maxRetries})`);
26956
+ logger7.warn("init_step_retry", {
26957
+ title,
26958
+ retry: retryIndex,
26959
+ retries: maxRetries,
26960
+ errorName: error instanceof Error ? error.name : void 0,
26961
+ errorMessage: error instanceof Error ? error.message : String(error),
26962
+ errorCode: getErrorCode2(error),
26963
+ ...error instanceof SpritesApiError ? { status: error.status } : {},
26964
+ ...parseExecUnexpectedResponseStatus(error) !== null ? { execStatus: parseExecUnexpectedResponseStatus(error) } : {}
26965
+ });
26966
+ await delay2(computeRetryDelayMs2(retryIndex));
26967
+ }
26968
+ }
26969
+ throw lastError instanceof Error ? lastError : new Error(`Init step failed: ${title}`);
26970
+ };
26794
26971
  migrateLegacyRepoDevboxDir = async ({
26795
26972
  repoRoot,
26796
26973
  projectDir
@@ -26965,15 +27142,27 @@ var init_init = __esm({
26965
27142
  client,
26966
27143
  canonical,
26967
27144
  workdir,
26968
- status,
26969
- json
27145
+ status
26970
27146
  }) => {
26971
27147
  const checkResult = await client.exec(canonical, [
26972
27148
  "/bin/bash",
26973
27149
  "-lc",
26974
- `if [ -d ${shellQuote3(workdir)} ]; then stat -c %U ${shellQuote3(workdir)}; fi`
27150
+ [
27151
+ "set -euo pipefail",
27152
+ `dir=${shellQuote3(workdir)}`,
27153
+ 'if [ ! -d "$dir" ]; then',
27154
+ ' echo "Missing workdir: $dir" >&2',
27155
+ " exit 1",
27156
+ "fi",
27157
+ 'stat -c %U "$dir"'
27158
+ ].join("\n")
26975
27159
  ]);
26976
- if (checkResult.exitCode !== 0) return;
27160
+ if (checkResult.exitCode !== 0) {
27161
+ const details = checkResult.stderr || checkResult.stdout || "";
27162
+ throw new Error(
27163
+ details ? `Failed to check workdir ownership: ${details.trim()}` : `Failed to check workdir ownership (exit ${checkResult.exitCode})`
27164
+ );
27165
+ }
26977
27166
  const owner = checkResult.stdout.trim();
26978
27167
  if (!owner || owner === "sprite") return;
26979
27168
  status.stage("Fixing workdir ownership");
@@ -26983,17 +27172,10 @@ var init_init = __esm({
26983
27172
  `sudo -n chown -R sprite:sprite ${shellQuote3(workdir)}`
26984
27173
  ]);
26985
27174
  if (chownResult.exitCode !== 0) {
26986
- logger7.warn("workdir_chown_failed", {
26987
- box: canonical,
26988
- workdir,
26989
- error: chownResult.stderr || chownResult.stdout
26990
- });
26991
- if (!json) {
26992
- status.stop();
26993
- console.warn(
26994
- "Warning: failed to update workdir ownership. Git may refuse to operate until ownership is fixed."
26995
- );
26996
- }
27175
+ const details = chownResult.stderr || chownResult.stdout || "";
27176
+ throw new Error(
27177
+ details ? `Failed to update workdir ownership: ${details.trim()}` : `Failed to update workdir ownership (exit ${chownResult.exitCode})`
27178
+ );
26997
27179
  }
26998
27180
  };
26999
27181
  ensureGitSafeDirectory = async ({
@@ -27357,7 +27539,7 @@ var init_init = __esm({
27357
27539
  fingerprint,
27358
27540
  phase: label
27359
27541
  });
27360
- return { id: null, comment, createdAt };
27542
+ throw new Error("Checkpoint ID missing.");
27361
27543
  }
27362
27544
  const record = { id, comment, createdAt };
27363
27545
  if (initState) {
@@ -27536,13 +27718,17 @@ var init_init = __esm({
27536
27718
  });
27537
27719
  await runInitStep({
27538
27720
  enabled: progressEnabled,
27539
- title: "Snapshotting filesystem (pre-setup) (optional)",
27540
- fn: async ({ fail, ok }) => {
27721
+ title: "Snapshotting filesystem (pre-setup)",
27722
+ fn: async ({ status, fail, ok }) => {
27541
27723
  try {
27542
- const checkpoint = await recordCodexCheckpoint({
27543
- client: client2,
27544
- canonical: canonical2,
27545
- phase: "preCodexSetup"
27724
+ const checkpoint = await retryInitStep({
27725
+ status,
27726
+ title: "Snapshotting filesystem (pre-setup)",
27727
+ fn: async () => await recordCodexCheckpoint({
27728
+ client: client2,
27729
+ canonical: canonical2,
27730
+ phase: "preCodexSetup"
27731
+ })
27546
27732
  });
27547
27733
  if (checkpoint.id) {
27548
27734
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -27554,12 +27740,8 @@ var init_init = __esm({
27554
27740
  phase: "pre-codex-setup",
27555
27741
  error: error instanceof Error ? error.message : String(error)
27556
27742
  });
27557
- fail("Snapshotting filesystem (pre-setup) (optional) (failed)");
27558
- if (!parsed.json) {
27559
- console.warn(
27560
- "Warning: failed to create pre-setup filesystem snapshot. Continuing without it."
27561
- );
27562
- }
27743
+ fail("Snapshotting filesystem (pre-setup) (failed)");
27744
+ throw error;
27563
27745
  }
27564
27746
  }
27565
27747
  });
@@ -27595,13 +27777,17 @@ var init_init = __esm({
27595
27777
  });
27596
27778
  await runInitStep({
27597
27779
  enabled: progressEnabled,
27598
- title: "Snapshotting filesystem (post-setup) (optional)",
27599
- fn: async ({ fail, ok }) => {
27780
+ title: "Snapshotting filesystem (post-setup)",
27781
+ fn: async ({ status, fail, ok }) => {
27600
27782
  try {
27601
- const checkpoint = await recordCodexCheckpoint({
27602
- client: client2,
27603
- canonical: canonical2,
27604
- phase: "postCodexSetup"
27783
+ const checkpoint = await retryInitStep({
27784
+ status,
27785
+ title: "Snapshotting filesystem (post-setup)",
27786
+ fn: async () => await recordCodexCheckpoint({
27787
+ client: client2,
27788
+ canonical: canonical2,
27789
+ phase: "postCodexSetup"
27790
+ })
27605
27791
  });
27606
27792
  if (checkpoint.id) {
27607
27793
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -27613,12 +27799,8 @@ var init_init = __esm({
27613
27799
  phase: "post-codex-setup",
27614
27800
  error: error instanceof Error ? error.message : String(error)
27615
27801
  });
27616
- fail("Snapshotting filesystem (post-setup) (optional) (failed)");
27617
- if (!parsed.json) {
27618
- console.warn(
27619
- "Warning: failed to create post-setup filesystem snapshot. Continuing without it."
27620
- );
27621
- }
27802
+ fail("Snapshotting filesystem (post-setup) (failed)");
27803
+ throw error;
27622
27804
  }
27623
27805
  }
27624
27806
  });
@@ -27784,40 +27966,33 @@ var init_init = __esm({
27784
27966
  localPaths: projectLocalPaths
27785
27967
  });
27786
27968
  const updateRegistryProjectStatus = async (initStatus) => {
27787
- try {
27788
- await requestJson3(
27789
- socketInfo.socketPath,
27790
- "POST",
27791
- "/registry/upsert",
27792
- DAEMON_TIMEOUT_MS.registry,
27793
- {
27794
- project: buildProjectEntry(initStatus)
27795
- }
27796
- );
27797
- } catch (error) {
27798
- logger7.warn("registry_project_update_failed", {
27799
- box: canonical,
27800
- status: initStatus,
27801
- error: String(error)
27802
- });
27803
- }
27969
+ await requestJson3(
27970
+ socketInfo.socketPath,
27971
+ "POST",
27972
+ "/registry/upsert",
27973
+ DAEMON_TIMEOUT_MS.registry,
27974
+ {
27975
+ project: buildProjectEntry(initStatus)
27976
+ }
27977
+ );
27804
27978
  };
27805
27979
  await runInitStep({
27806
27980
  enabled: progressEnabled,
27807
27981
  title: "Bootstrapping devbox",
27808
- fn: async ({ fail }) => {
27982
+ fn: async ({ status, fail }) => {
27809
27983
  try {
27810
- await bootstrapDevbox(client, canonical);
27984
+ await retryInitStep({
27985
+ status,
27986
+ title: "Bootstrapping devbox",
27987
+ fn: async () => await bootstrapDevbox(client, canonical)
27988
+ });
27811
27989
  } catch (error) {
27812
27990
  logger7.warn("devbox_bootstrap_failed", {
27813
27991
  box: canonical,
27814
27992
  error: String(error)
27815
27993
  });
27816
27994
  fail("Bootstrapping devbox (failed)");
27817
- if (!parsed.json) {
27818
- const message = error instanceof Error && error.message ? error.message : String(error);
27819
- console.warn(`Warning: devbox bootstrap failed. ${message}`);
27820
- }
27995
+ throw error;
27821
27996
  }
27822
27997
  }
27823
27998
  });
@@ -27858,7 +28033,7 @@ var init_init = __esm({
27858
28033
  await runInitStep({
27859
28034
  enabled: progressEnabled,
27860
28035
  title: "Installing sprite daemon",
27861
- fn: async ({ fail }) => {
28036
+ fn: async ({ status, fail }) => {
27862
28037
  try {
27863
28038
  const convexUrl = getConvexUrl();
27864
28039
  if (!controlPlaneToken) {
@@ -27880,12 +28055,16 @@ var init_init = __esm({
27880
28055
  if (!heartbeatToken) {
27881
28056
  throw new Error("Daemon token unavailable.");
27882
28057
  }
27883
- await installSpriteDaemon({
27884
- client,
27885
- spriteName: canonical,
27886
- release,
27887
- convexUrl,
27888
- heartbeatToken
28058
+ await retryInitStep({
28059
+ status,
28060
+ title: "Installing sprite daemon",
28061
+ fn: async () => await installSpriteDaemon({
28062
+ client,
28063
+ spriteName: canonical,
28064
+ release,
28065
+ convexUrl,
28066
+ heartbeatToken
28067
+ })
27889
28068
  });
27890
28069
  await updateInitState({ steps: { daemonInstalled: true } });
27891
28070
  } catch (error) {
@@ -27894,12 +28073,7 @@ var init_init = __esm({
27894
28073
  error: String(error)
27895
28074
  });
27896
28075
  fail("Installing sprite daemon (failed)");
27897
- if (!parsed.json) {
27898
- const message = error instanceof Error && error.message ? error.message : String(error);
27899
- console.warn(
27900
- `Warning: failed to install sprite daemon. ${message}`
27901
- );
27902
- }
28076
+ throw error;
27903
28077
  }
27904
28078
  }
27905
28079
  });
@@ -27907,9 +28081,13 @@ var init_init = __esm({
27907
28081
  await runInitStep({
27908
28082
  enabled: progressEnabled,
27909
28083
  title: "Ensuring sprite daemon service",
27910
- fn: async ({ fail }) => {
28084
+ fn: async ({ status, fail }) => {
27911
28085
  try {
27912
- await ensureSpriteDaemonService({ client, spriteName: canonical });
28086
+ await retryInitStep({
28087
+ status,
28088
+ title: "Ensuring sprite daemon service",
28089
+ fn: async () => await ensureSpriteDaemonService({ client, spriteName: canonical })
28090
+ });
27913
28091
  await updateInitState({ steps: { daemonServiceEnsured: true } });
27914
28092
  } catch (error) {
27915
28093
  logger7.warn("sprite_daemon_service_failed", {
@@ -27917,12 +28095,7 @@ var init_init = __esm({
27917
28095
  error: String(error)
27918
28096
  });
27919
28097
  fail("Ensuring sprite daemon service (failed)");
27920
- if (!parsed.json) {
27921
- const message = error instanceof Error && error.message ? error.message : String(error);
27922
- console.warn(
27923
- `Warning: failed to ensure sprite daemon service. ${message}`
27924
- );
27925
- }
28098
+ throw error;
27926
28099
  }
27927
28100
  }
27928
28101
  });
@@ -28792,12 +28965,15 @@ var init_init = __esm({
28792
28965
  enabled: progressEnabled,
28793
28966
  title: "Ensuring workdir ownership",
28794
28967
  fn: async ({ status }) => {
28795
- await ensureWorkdirOwnership({
28796
- client,
28797
- canonical,
28798
- workdir: expandedWorkdir,
28968
+ await retryInitStep({
28799
28969
  status,
28800
- json: parsed.json === true
28970
+ title: "Ensuring workdir ownership",
28971
+ fn: async () => await ensureWorkdirOwnership({
28972
+ client,
28973
+ canonical,
28974
+ workdir: expandedWorkdir,
28975
+ status
28976
+ })
28801
28977
  });
28802
28978
  }
28803
28979
  });
@@ -28822,13 +28998,19 @@ var init_init = __esm({
28822
28998
  await runInitStep({
28823
28999
  enabled: progressEnabled,
28824
29000
  title: "Configuring SSH mount access",
28825
- fn: async ({ fail }) => {
29001
+ fn: async ({ status, fail }) => {
28826
29002
  try {
28827
- await ensureLocalMountKey();
28828
- await ensureKnownHostsFile();
28829
- const publicKey = await readLocalMountPublicKey();
28830
- await ensureRemoteMountAccess(client, canonical, publicKey);
28831
- await ensureSshdService(client, canonical);
29003
+ await retryInitStep({
29004
+ status,
29005
+ title: "Configuring SSH mount access",
29006
+ fn: async () => {
29007
+ await ensureLocalMountKey();
29008
+ await ensureKnownHostsFile();
29009
+ const publicKey = await readLocalMountPublicKey();
29010
+ await ensureRemoteMountAccess(client, canonical, publicKey);
29011
+ await ensureSshdService(client, canonical);
29012
+ }
29013
+ });
28832
29014
  await updateInitState({ steps: { sshdConfigured: true } });
28833
29015
  } catch (error) {
28834
29016
  logger7.warn("sshd_config_failed", {
@@ -28836,13 +29018,7 @@ var init_init = __esm({
28836
29018
  error: String(error)
28837
29019
  });
28838
29020
  fail("Configuring SSH mount access (failed)");
28839
- if (!parsed.json) {
28840
- const message = error instanceof Error && error.message ? error.message : String(error);
28841
- console.warn(
28842
- `Warning: failed to configure SSH mount access. ${message}`
28843
- );
28844
- console.warn(`To retry later: dvb mount ${alias}`);
28845
- }
29021
+ throw error;
28846
29022
  }
28847
29023
  }
28848
29024
  });
@@ -29000,10 +29176,12 @@ var init_init = __esm({
29000
29176
  }
29001
29177
  const weztermMuxPresent = await runInitStep({
29002
29178
  enabled: progressEnabled,
29003
- title: "Ensuring WezTerm mux server (optional)",
29004
- fn: async ({ fail }) => {
29005
- try {
29006
- const installResult = !shouldResume || !initState?.steps.weztermMuxInstalled ? await ensureWeztermMuxInstalled({
29179
+ title: "Ensuring WezTerm mux server",
29180
+ fn: async ({ status }) => {
29181
+ const installResult = await retryInitStep({
29182
+ status,
29183
+ title: "Ensuring WezTerm mux server",
29184
+ fn: async () => !shouldResume || !initState?.steps.weztermMuxInstalled ? await ensureWeztermMuxInstalled({
29007
29185
  client,
29008
29186
  spriteName: canonical,
29009
29187
  allowResolveAsset: true
@@ -29012,49 +29190,29 @@ var init_init = __esm({
29012
29190
  spriteName: canonical,
29013
29191
  // If the mux is missing on resume, we still need to resolve a GitHub asset.
29014
29192
  allowResolveAsset: true
29015
- });
29016
- if (installResult.muxPresent) {
29017
- await updateInitState({ steps: { weztermMuxInstalled: true } });
29018
- }
29019
- return installResult.muxPresent;
29020
- } catch (error) {
29021
- logger7.warn("wezterm_mux_install_failed", {
29022
- box: canonical,
29023
- error: error instanceof Error ? error.message : String(error)
29024
- });
29025
- fail("Ensuring WezTerm mux server (optional) (failed)");
29026
- if (!parsed.json) {
29027
- const message = error instanceof Error && error.message ? error.message : String(error);
29028
- console.warn(
29029
- `Warning: failed to install WezTerm mux server (optional). ${message}
29030
- Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.`
29031
- );
29032
- }
29033
- return false;
29193
+ })
29194
+ });
29195
+ if (installResult.muxPresent) {
29196
+ await updateInitState({ steps: { weztermMuxInstalled: true } });
29197
+ return true;
29034
29198
  }
29199
+ throw new Error("WezTerm mux server unavailable.");
29035
29200
  }
29036
29201
  });
29037
- if (weztermMuxPresent && (!shouldResume || !initState?.steps.weztermMuxServiceEnsured)) {
29202
+ if (!shouldResume || !initState?.steps.weztermMuxServiceEnsured) {
29038
29203
  await runInitStep({
29039
29204
  enabled: progressEnabled,
29040
- title: "Ensuring WezTerm mux service (optional)",
29041
- fn: async ({ fail }) => {
29042
- try {
29043
- await ensureWeztermMuxService({ client, spriteName: canonical });
29044
- await updateInitState({ steps: { weztermMuxServiceEnsured: true } });
29045
- } catch (error) {
29046
- logger7.warn("wezterm_mux_service_failed", {
29047
- box: canonical,
29048
- error: error instanceof Error ? error.message : String(error)
29049
- });
29050
- fail("Ensuring WezTerm mux service (optional) (failed)");
29051
- if (!parsed.json) {
29052
- const message = error instanceof Error && error.message ? error.message : String(error);
29053
- console.warn(
29054
- `Warning: failed to ensure WezTerm mux service (optional). ${message}`
29055
- );
29056
- }
29205
+ title: "Ensuring WezTerm mux service",
29206
+ fn: async ({ status }) => {
29207
+ if (!weztermMuxPresent) {
29208
+ throw new Error("WezTerm mux server unavailable.");
29057
29209
  }
29210
+ await retryInitStep({
29211
+ status,
29212
+ title: "Ensuring WezTerm mux service",
29213
+ fn: async () => await ensureWeztermMuxService({ client, spriteName: canonical })
29214
+ });
29215
+ await updateInitState({ steps: { weztermMuxServiceEnsured: true } });
29058
29216
  }
29059
29217
  });
29060
29218
  }
@@ -29121,21 +29279,13 @@ Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.`
29121
29279
  });
29122
29280
  await runInitStep({
29123
29281
  enabled: progressEnabled,
29124
- title: "Updating Codex config on sprite (optional)",
29125
- fn: async ({ fail }) => {
29126
- try {
29127
- await writeRemoteCodexConfig(client, canonical, repoName);
29128
- } catch (error) {
29129
- logger7.warn("codex_config_update_failed", {
29130
- error: String(error)
29131
- });
29132
- fail("Updating Codex config on sprite (optional) (failed)");
29133
- if (!parsed.json) {
29134
- console.warn(
29135
- "Warning: failed to update /home/sprite/.codex/config.toml for full-access sandbox settings."
29136
- );
29137
- }
29138
- }
29282
+ title: "Updating Codex config on sprite",
29283
+ fn: async ({ status }) => {
29284
+ await retryInitStep({
29285
+ status,
29286
+ title: "Updating Codex config on sprite",
29287
+ fn: async () => await writeRemoteCodexConfig(client, canonical, repoName)
29288
+ });
29139
29289
  }
29140
29290
  });
29141
29291
  const skipSetupArtifactsStage = skipCodexApply || shouldResume && initState?.steps.setupArtifactsStaged;
@@ -29159,13 +29309,17 @@ Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.`
29159
29309
  if (!skipCodexApply) {
29160
29310
  await runInitStep({
29161
29311
  enabled: progressEnabled,
29162
- title: "Snapshotting filesystem (pre-setup) (optional)",
29163
- fn: async ({ fail, ok }) => {
29312
+ title: "Snapshotting filesystem (pre-setup)",
29313
+ fn: async ({ status, fail, ok }) => {
29164
29314
  try {
29165
- const checkpoint = await recordCodexCheckpoint({
29166
- client,
29167
- canonical,
29168
- phase: "preCodexSetup"
29315
+ const checkpoint = await retryInitStep({
29316
+ status,
29317
+ title: "Snapshotting filesystem (pre-setup)",
29318
+ fn: async () => await recordCodexCheckpoint({
29319
+ client,
29320
+ canonical,
29321
+ phase: "preCodexSetup"
29322
+ })
29169
29323
  });
29170
29324
  if (checkpoint.id) {
29171
29325
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -29177,12 +29331,8 @@ Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.`
29177
29331
  phase: "pre-codex-setup",
29178
29332
  error: error instanceof Error ? error.message : String(error)
29179
29333
  });
29180
- fail("Snapshotting filesystem (pre-setup) (optional) (failed)");
29181
- if (!parsed.json) {
29182
- console.warn(
29183
- "Warning: failed to create pre-setup filesystem snapshot. Continuing without it."
29184
- );
29185
- }
29334
+ fail("Snapshotting filesystem (pre-setup) (failed)");
29335
+ throw error;
29186
29336
  }
29187
29337
  }
29188
29338
  });
@@ -29225,13 +29375,17 @@ Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.`
29225
29375
  );
29226
29376
  await runInitStep({
29227
29377
  enabled: progressEnabled,
29228
- title: "Snapshotting filesystem (post-setup) (optional)",
29229
- fn: async ({ fail, ok }) => {
29378
+ title: "Snapshotting filesystem (post-setup)",
29379
+ fn: async ({ status, fail, ok }) => {
29230
29380
  try {
29231
- const checkpoint = await recordCodexCheckpoint({
29232
- client,
29233
- canonical,
29234
- phase: "postCodexSetup"
29381
+ const checkpoint = await retryInitStep({
29382
+ status,
29383
+ title: "Snapshotting filesystem (post-setup)",
29384
+ fn: async () => await recordCodexCheckpoint({
29385
+ client,
29386
+ canonical,
29387
+ phase: "postCodexSetup"
29388
+ })
29235
29389
  });
29236
29390
  if (checkpoint.id) {
29237
29391
  ok(`Snapshot created: ${checkpoint.id}`);
@@ -29243,12 +29397,8 @@ Tip: re-run with DEVBOX_LOG_LEVEL=info to see Sprite exec logs on stderr.`
29243
29397
  phase: "post-codex-setup",
29244
29398
  error: error instanceof Error ? error.message : String(error)
29245
29399
  });
29246
- fail("Snapshotting filesystem (post-setup) (optional) (failed)");
29247
- if (!parsed.json) {
29248
- console.warn(
29249
- "Warning: failed to create post-setup filesystem snapshot. Continuing without it."
29250
- );
29251
- }
29400
+ fail("Snapshotting filesystem (post-setup) (failed)");
29401
+ throw error;
29252
29402
  }
29253
29403
  }
29254
29404
  });
@@ -32254,7 +32404,7 @@ var init_cli = __esm({
32254
32404
  });
32255
32405
 
32256
32406
  // src/bin/dvb.ts
32257
- var import_node_fs7 = __toESM(require("node:fs"), 1);
32407
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
32258
32408
  var import_node_os11 = __toESM(require("node:os"), 1);
32259
32409
  var import_node_path26 = __toESM(require("node:path"), 1);
32260
32410
  init_src();
@@ -32312,8 +32462,8 @@ run().catch((error) => {
32312
32462
  if (homeDir) {
32313
32463
  try {
32314
32464
  const logDir = import_node_path26.default.join(resolveDevboxDir(homeDir), "wezterm");
32315
- import_node_fs7.default.mkdirSync(logDir, { recursive: true });
32316
- import_node_fs7.default.appendFileSync(
32465
+ import_node_fs8.default.mkdirSync(logDir, { recursive: true });
32466
+ import_node_fs8.default.appendFileSync(
32317
32467
  import_node_path26.default.join(logDir, "proxy.log"),
32318
32468
  `${(/* @__PURE__ */ new Date()).toISOString()} ${message}
32319
32469
  `