@prover-coder-ai/docker-git 1.0.35 → 1.0.37

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.
@@ -345,6 +345,228 @@ const runCommandCapture = (spec, okExitCodes, onFailure) => Effect.scoped(
345
345
  return new TextDecoder("utf-8").decode(bytes);
346
346
  })
347
347
  );
348
+ const resolveDockerEnvValue = (key) => {
349
+ const value = process.env[key]?.trim();
350
+ return value && value.length > 0 ? value : null;
351
+ };
352
+ const trimDockerPathTrailingSlash = (value) => {
353
+ let end = value.length;
354
+ while (end > 0) {
355
+ const char = value[end - 1];
356
+ if (char !== "/" && char !== "\\") {
357
+ break;
358
+ }
359
+ end -= 1;
360
+ }
361
+ return value.slice(0, end);
362
+ };
363
+ const pathStartsWith = (candidate, prefix) => candidate === prefix || candidate.startsWith(`${prefix}/`) || candidate.startsWith(`${prefix}\\`);
364
+ const translatePathPrefix = (candidate, sourcePrefix, targetPrefix) => pathStartsWith(candidate, sourcePrefix) ? `${targetPrefix}${candidate.slice(sourcePrefix.length)}` : null;
365
+ const resolveContainerProjectsRoot = () => {
366
+ const explicit = resolveDockerEnvValue("DOCKER_GIT_PROJECTS_ROOT");
367
+ if (explicit !== null) {
368
+ return explicit;
369
+ }
370
+ const home = resolveDockerEnvValue("HOME") ?? resolveDockerEnvValue("USERPROFILE");
371
+ return home === null ? null : `${trimDockerPathTrailingSlash(home)}/.docker-git`;
372
+ };
373
+ const resolveProjectsRootHostOverride = () => resolveDockerEnvValue("DOCKER_GIT_PROJECTS_ROOT_HOST");
374
+ const resolveCurrentContainerId = (cwd) => {
375
+ const fromEnv = resolveDockerEnvValue("HOSTNAME");
376
+ if (fromEnv !== null) {
377
+ return Effect.succeed(fromEnv);
378
+ }
379
+ return runCommandCapture(
380
+ {
381
+ cwd,
382
+ command: "hostname",
383
+ args: []
384
+ },
385
+ [0],
386
+ () => new Error("hostname failed")
387
+ ).pipe(
388
+ Effect.map((value) => value.trim()),
389
+ Effect.orElseSucceed(() => ""),
390
+ Effect.map((value) => value.length > 0 ? value : null)
391
+ );
392
+ };
393
+ const parseDockerInspectMounts = (raw) => raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
394
+ const separator = line.indexOf(" ");
395
+ if (separator <= 0 || separator >= line.length - 1) {
396
+ return [];
397
+ }
398
+ const source = line.slice(0, separator).trim();
399
+ const destination = line.slice(separator + 1).trim();
400
+ if (source.length === 0 || destination.length === 0) {
401
+ return [];
402
+ }
403
+ return [{ source, destination }];
404
+ });
405
+ const remapDockerBindHostPathFromMounts = (hostPath, mounts) => {
406
+ let match = null;
407
+ for (const mount of mounts) {
408
+ if (!pathStartsWith(hostPath, mount.destination)) {
409
+ continue;
410
+ }
411
+ if (match === null || mount.destination.length > match.destination.length) {
412
+ match = mount;
413
+ }
414
+ }
415
+ if (match === null) {
416
+ return hostPath;
417
+ }
418
+ return `${match.source}${hostPath.slice(match.destination.length)}`;
419
+ };
420
+ const resolveDockerVolumeHostPath = (cwd, hostPath) => Effect.gen(function* (_) {
421
+ const containerProjectsRoot = resolveContainerProjectsRoot();
422
+ const hostProjectsRoot = resolveProjectsRootHostOverride();
423
+ if (containerProjectsRoot !== null && hostProjectsRoot !== null) {
424
+ const remapped = translatePathPrefix(hostPath, containerProjectsRoot, hostProjectsRoot);
425
+ if (remapped !== null) {
426
+ return remapped;
427
+ }
428
+ }
429
+ const containerId = yield* _(resolveCurrentContainerId(cwd));
430
+ if (containerId === null) {
431
+ return hostPath;
432
+ }
433
+ const mountsJson = yield* _(
434
+ runCommandCapture(
435
+ {
436
+ cwd,
437
+ command: "docker",
438
+ args: [
439
+ "inspect",
440
+ containerId,
441
+ "--format",
442
+ String.raw`{{range .Mounts}}{{println .Source "\t" .Destination}}{{end}}`
443
+ ]
444
+ },
445
+ [0],
446
+ () => new Error("docker inspect current container failed")
447
+ ).pipe(Effect.orElseSucceed(() => ""))
448
+ );
449
+ return remapDockerBindHostPathFromMounts(hostPath, parseDockerInspectMounts(mountsJson));
450
+ });
451
+ const resolveDefaultDockerUser = () => {
452
+ const getUid = Reflect.get(process, "getuid");
453
+ const getGid = Reflect.get(process, "getgid");
454
+ if (typeof getUid !== "function" || typeof getGid !== "function") {
455
+ return null;
456
+ }
457
+ const uid = getUid.call(process);
458
+ const gid = getGid.call(process);
459
+ if (typeof uid !== "number" || typeof gid !== "number") {
460
+ return null;
461
+ }
462
+ return `${uid}:${gid}`;
463
+ };
464
+ const appendEnvArgs = (base, env) => {
465
+ if (typeof env === "string") {
466
+ const trimmed = env.trim();
467
+ if (trimmed.length > 0) {
468
+ base.push("-e", trimmed);
469
+ }
470
+ return;
471
+ }
472
+ for (const entry of env) {
473
+ const trimmed = entry.trim();
474
+ if (trimmed.length === 0) {
475
+ continue;
476
+ }
477
+ base.push("-e", trimmed);
478
+ }
479
+ };
480
+ const buildDockerArgs = (spec) => {
481
+ const base = ["run", "--rm"];
482
+ const dockerUser = (spec.user ?? "").trim() || resolveDefaultDockerUser();
483
+ if (dockerUser !== null) {
484
+ base.push("--user", dockerUser);
485
+ }
486
+ if (spec.interactive) {
487
+ base.push("-it");
488
+ }
489
+ if (spec.entrypoint && spec.entrypoint.length > 0) {
490
+ base.push("--entrypoint", spec.entrypoint);
491
+ }
492
+ base.push("-v", `${spec.volume.hostPath}:${spec.volume.containerPath}`);
493
+ if (spec.env !== void 0) {
494
+ appendEnvArgs(base, spec.env);
495
+ }
496
+ return [...base, spec.image, ...spec.args];
497
+ };
498
+ const runDockerAuth = (spec, okExitCodes, onFailure) => Effect.gen(function* (_) {
499
+ const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
500
+ yield* _(
501
+ runCommandWithExitCodes(
502
+ {
503
+ cwd: spec.cwd,
504
+ command: "docker",
505
+ args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
506
+ },
507
+ okExitCodes,
508
+ onFailure
509
+ )
510
+ );
511
+ });
512
+ const runDockerAuthCapture = (spec, okExitCodes, onFailure) => Effect.gen(function* (_) {
513
+ const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
514
+ return yield* _(
515
+ runCommandCapture(
516
+ {
517
+ cwd: spec.cwd,
518
+ command: "docker",
519
+ args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
520
+ },
521
+ okExitCodes,
522
+ onFailure
523
+ )
524
+ );
525
+ });
526
+ const runDockerAuthExitCode = (spec) => Effect.gen(function* (_) {
527
+ const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
528
+ return yield* _(
529
+ runCommandExitCode({
530
+ cwd: spec.cwd,
531
+ command: "docker",
532
+ args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
533
+ })
534
+ );
535
+ });
536
+ const composeSpec = (cwd, args) => ({
537
+ cwd,
538
+ command: "docker",
539
+ args: ["compose", "--ansi", "never", "--progress", "plain", ...args]
540
+ });
541
+ const resolveProjectsRootCandidate = () => {
542
+ const explicit = resolveDockerEnvValue("DOCKER_GIT_PROJECTS_ROOT");
543
+ if (explicit !== null) {
544
+ return explicit;
545
+ }
546
+ const home = resolveDockerEnvValue("HOME") ?? resolveDockerEnvValue("USERPROFILE");
547
+ return home === null ? null : `${trimDockerPathTrailingSlash(home)}/.docker-git`;
548
+ };
549
+ const resolveDockerComposeEnv = (cwd) => Effect.gen(function* (_) {
550
+ const projectsRoot = resolveProjectsRootCandidate();
551
+ if (projectsRoot === null) {
552
+ return {};
553
+ }
554
+ const remappedProjectsRoot = yield* _(resolveDockerVolumeHostPath(cwd, projectsRoot));
555
+ return remappedProjectsRoot === projectsRoot ? {} : { DOCKER_GIT_PROJECTS_ROOT_HOST: remappedProjectsRoot };
556
+ });
557
+ const parseInspectNetworkEntry = (line) => {
558
+ const idx = line.indexOf("=");
559
+ if (idx <= 0) {
560
+ return [];
561
+ }
562
+ const network = line.slice(0, idx).trim();
563
+ const ip = line.slice(idx + 1).trim();
564
+ if (network.length === 0 || ip.length === 0) {
565
+ return [];
566
+ }
567
+ const entry = [network, ip];
568
+ return [entry];
569
+ };
348
570
  class FileExistsError extends Data.TaggedError("FileExistsError") {
349
571
  }
350
572
  class ConfigNotFoundError extends Data.TaggedError("ConfigNotFoundError") {
@@ -513,34 +735,32 @@ const runDockerPsPublishedHostPorts = (cwd) => pipe(
513
735
  ),
514
736
  Effect.map((output) => parseDockerPublishedHostPorts(output))
515
737
  );
516
- const composeSpec = (cwd, args) => ({
517
- cwd,
518
- command: "docker",
519
- args: ["compose", "--ansi", "never", "--progress", "plain", ...args]
738
+ const runCompose = (cwd, args, okExitCodes) => Effect.gen(function* (_) {
739
+ const env = yield* _(resolveDockerComposeEnv(cwd));
740
+ yield* _(
741
+ runCommandWithExitCodes(
742
+ {
743
+ ...composeSpec(cwd, args),
744
+ ...Object.keys(env).length > 0 ? { env } : {}
745
+ },
746
+ okExitCodes,
747
+ (exitCode) => new DockerCommandError({ exitCode })
748
+ )
749
+ );
750
+ });
751
+ const runComposeCapture = (cwd, args, okExitCodes) => Effect.gen(function* (_) {
752
+ const env = yield* _(resolveDockerComposeEnv(cwd));
753
+ return yield* _(
754
+ runCommandCapture(
755
+ {
756
+ ...composeSpec(cwd, args),
757
+ ...Object.keys(env).length > 0 ? { env } : {}
758
+ },
759
+ okExitCodes,
760
+ (exitCode) => new DockerCommandError({ exitCode })
761
+ )
762
+ );
520
763
  });
521
- const parseInspectNetworkEntry = (line) => {
522
- const idx = line.indexOf("=");
523
- if (idx <= 0) {
524
- return [];
525
- }
526
- const network = line.slice(0, idx).trim();
527
- const ip = line.slice(idx + 1).trim();
528
- if (network.length === 0 || ip.length === 0) {
529
- return [];
530
- }
531
- const entry = [network, ip];
532
- return [entry];
533
- };
534
- const runCompose = (cwd, args, okExitCodes) => runCommandWithExitCodes(
535
- composeSpec(cwd, args),
536
- okExitCodes,
537
- (exitCode) => new DockerCommandError({ exitCode })
538
- );
539
- const runComposeCapture = (cwd, args, okExitCodes) => runCommandCapture(
540
- composeSpec(cwd, args),
541
- okExitCodes,
542
- (exitCode) => new DockerCommandError({ exitCode })
543
- );
544
764
  const dockerComposeUpRetrySchedule = Schedule.addDelay(
545
765
  Schedule.recurs(2),
546
766
  () => Duration.seconds(2)
@@ -851,194 +1071,6 @@ const renderError = (error) => {
851
1071
  }
852
1072
  return renderNonParseError(error);
853
1073
  };
854
- const resolveEnvValue = (key) => {
855
- const value = process.env[key]?.trim();
856
- return value && value.length > 0 ? value : null;
857
- };
858
- const trimTrailingSlash$1 = (value) => {
859
- let end = value.length;
860
- while (end > 0) {
861
- const char = value[end - 1];
862
- if (char !== "/" && char !== "\\") {
863
- break;
864
- }
865
- end -= 1;
866
- }
867
- return value.slice(0, end);
868
- };
869
- const pathStartsWith = (candidate, prefix) => candidate === prefix || candidate.startsWith(`${prefix}/`) || candidate.startsWith(`${prefix}\\`);
870
- const translatePathPrefix = (candidate, sourcePrefix, targetPrefix) => pathStartsWith(candidate, sourcePrefix) ? `${targetPrefix}${candidate.slice(sourcePrefix.length)}` : null;
871
- const resolveContainerProjectsRoot = () => {
872
- const explicit = resolveEnvValue("DOCKER_GIT_PROJECTS_ROOT");
873
- if (explicit !== null) {
874
- return explicit;
875
- }
876
- const home = resolveEnvValue("HOME") ?? resolveEnvValue("USERPROFILE");
877
- return home === null ? null : `${trimTrailingSlash$1(home)}/.docker-git`;
878
- };
879
- const resolveProjectsRootHostOverride = () => resolveEnvValue("DOCKER_GIT_PROJECTS_ROOT_HOST");
880
- const resolveCurrentContainerId = (cwd) => {
881
- const fromEnv = resolveEnvValue("HOSTNAME");
882
- if (fromEnv !== null) {
883
- return Effect.succeed(fromEnv);
884
- }
885
- return runCommandCapture(
886
- {
887
- cwd,
888
- command: "hostname",
889
- args: []
890
- },
891
- [0],
892
- () => new Error("hostname failed")
893
- ).pipe(
894
- Effect.map((value) => value.trim()),
895
- Effect.orElseSucceed(() => ""),
896
- Effect.map((value) => value.length > 0 ? value : null)
897
- );
898
- };
899
- const parseDockerInspectMounts = (raw) => raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
900
- const separator = line.indexOf(" ");
901
- if (separator <= 0 || separator >= line.length - 1) {
902
- return [];
903
- }
904
- const source = line.slice(0, separator).trim();
905
- const destination = line.slice(separator + 1).trim();
906
- if (source.length === 0 || destination.length === 0) {
907
- return [];
908
- }
909
- return [{ source, destination }];
910
- });
911
- const remapDockerBindHostPathFromMounts = (hostPath, mounts) => {
912
- let match = null;
913
- for (const mount of mounts) {
914
- if (!pathStartsWith(hostPath, mount.destination)) {
915
- continue;
916
- }
917
- if (match === null || mount.destination.length > match.destination.length) {
918
- match = mount;
919
- }
920
- }
921
- if (match === null) {
922
- return hostPath;
923
- }
924
- return `${match.source}${hostPath.slice(match.destination.length)}`;
925
- };
926
- const resolveDockerVolumeHostPath = (cwd, hostPath) => Effect.gen(function* (_) {
927
- const containerProjectsRoot = resolveContainerProjectsRoot();
928
- const hostProjectsRoot = resolveProjectsRootHostOverride();
929
- if (containerProjectsRoot !== null && hostProjectsRoot !== null) {
930
- const remapped = translatePathPrefix(hostPath, containerProjectsRoot, hostProjectsRoot);
931
- if (remapped !== null) {
932
- return remapped;
933
- }
934
- }
935
- const containerId = yield* _(resolveCurrentContainerId(cwd));
936
- if (containerId === null) {
937
- return hostPath;
938
- }
939
- const mountsJson = yield* _(
940
- runCommandCapture(
941
- {
942
- cwd,
943
- command: "docker",
944
- args: [
945
- "inspect",
946
- containerId,
947
- "--format",
948
- String.raw`{{range .Mounts}}{{println .Source "\t" .Destination}}{{end}}`
949
- ]
950
- },
951
- [0],
952
- () => new Error("docker inspect current container failed")
953
- ).pipe(Effect.orElseSucceed(() => ""))
954
- );
955
- return remapDockerBindHostPathFromMounts(hostPath, parseDockerInspectMounts(mountsJson));
956
- });
957
- const resolveDefaultDockerUser = () => {
958
- const getUid = Reflect.get(process, "getuid");
959
- const getGid = Reflect.get(process, "getgid");
960
- if (typeof getUid !== "function" || typeof getGid !== "function") {
961
- return null;
962
- }
963
- const uid = getUid.call(process);
964
- const gid = getGid.call(process);
965
- if (typeof uid !== "number" || typeof gid !== "number") {
966
- return null;
967
- }
968
- return `${uid}:${gid}`;
969
- };
970
- const appendEnvArgs = (base, env) => {
971
- if (typeof env === "string") {
972
- const trimmed = env.trim();
973
- if (trimmed.length > 0) {
974
- base.push("-e", trimmed);
975
- }
976
- return;
977
- }
978
- for (const entry of env) {
979
- const trimmed = entry.trim();
980
- if (trimmed.length === 0) {
981
- continue;
982
- }
983
- base.push("-e", trimmed);
984
- }
985
- };
986
- const buildDockerArgs = (spec) => {
987
- const base = ["run", "--rm"];
988
- const dockerUser = (spec.user ?? "").trim() || resolveDefaultDockerUser();
989
- if (dockerUser !== null) {
990
- base.push("--user", dockerUser);
991
- }
992
- if (spec.interactive) {
993
- base.push("-it");
994
- }
995
- if (spec.entrypoint && spec.entrypoint.length > 0) {
996
- base.push("--entrypoint", spec.entrypoint);
997
- }
998
- base.push("-v", `${spec.volume.hostPath}:${spec.volume.containerPath}`);
999
- if (spec.env !== void 0) {
1000
- appendEnvArgs(base, spec.env);
1001
- }
1002
- return [...base, spec.image, ...spec.args];
1003
- };
1004
- const runDockerAuth = (spec, okExitCodes, onFailure) => Effect.gen(function* (_) {
1005
- const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
1006
- yield* _(
1007
- runCommandWithExitCodes(
1008
- {
1009
- cwd: spec.cwd,
1010
- command: "docker",
1011
- args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
1012
- },
1013
- okExitCodes,
1014
- onFailure
1015
- )
1016
- );
1017
- });
1018
- const runDockerAuthCapture = (spec, okExitCodes, onFailure) => Effect.gen(function* (_) {
1019
- const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
1020
- return yield* _(
1021
- runCommandCapture(
1022
- {
1023
- cwd: spec.cwd,
1024
- command: "docker",
1025
- args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
1026
- },
1027
- okExitCodes,
1028
- onFailure
1029
- )
1030
- );
1031
- });
1032
- const runDockerAuthExitCode = (spec) => Effect.gen(function* (_) {
1033
- const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
1034
- return yield* _(
1035
- runCommandExitCode({
1036
- cwd: spec.cwd,
1037
- command: "docker",
1038
- args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
1039
- })
1040
- );
1041
- });
1042
1074
  const normalizeAccountLabel = (value, fallback) => {
1043
1075
  const trimmed = value?.trim() ?? "";
1044
1076
  if (trimmed.length === 0) {
@@ -3622,15 +3654,15 @@ if [[ -n "$AGENT_PROMPT" ]]; then
3622
3654
  chmod 644 "$AGENT_PROMPT_FILE"
3623
3655
  fi`
3624
3656
  ].join("\n\n");
3625
- const renderAgentPromptCommand = (mode) => mode === "claude" ? String.raw`claude --dangerously-skip-permissions -p \"\$(cat $AGENT_PROMPT_FILE)\"` : String.raw`codex --approval-mode full-auto \"\$(cat $AGENT_PROMPT_FILE)\"`;
3657
+ const renderAgentPromptCommand = (mode) => mode === "claude" ? String.raw`claude --dangerously-skip-permissions -p \"\$(cat \"$AGENT_PROMPT_FILE\")\"` : String.raw`codex exec \"\$(cat \"$AGENT_PROMPT_FILE\")\"`;
3658
+ const renderAgentAutoLaunchCommand = (config, mode) => String.raw`su - ${config.sshUser} -s /bin/bash -c "bash -lc '. /etc/profile 2>/dev/null || true; . \"$AGENT_ENV_FILE\" 2>/dev/null || true; cd \"$TARGET_DIR\" && ${renderAgentPromptCommand(mode)}'"`;
3626
3659
  const renderAgentModeBlock = (config, mode) => {
3627
3660
  const startMessage = `[agent] starting ${mode}...`;
3628
3661
  const interactiveMessage = `[agent] ${mode} started in interactive mode (use SSH to connect)`;
3629
3662
  return String.raw`"${mode}")
3630
3663
  echo "${startMessage}"
3631
3664
  if [[ -n "$AGENT_PROMPT" ]]; then
3632
- if su - ${config.sshUser} \
3633
- -c ". /run/docker-git/agent-env.sh 2>/dev/null; cd '$TARGET_DIR' && ${renderAgentPromptCommand(mode)}"; then
3665
+ if ${renderAgentAutoLaunchCommand(config, mode)}; then
3634
3666
  AGENT_OK=1
3635
3667
  fi
3636
3668
  else
@@ -3961,6 +3993,8 @@ const renderAgentModeEnv = (agentMode) => agentMode !== void 0 && agentMode.leng
3961
3993
  ` : "";
3962
3994
  const renderAgentAutoEnv = (agentAuto) => agentAuto === true ? ` AGENT_AUTO: "1"
3963
3995
  ` : "";
3996
+ const renderProjectsRootHostMount = (projectsRoot) => `\${DOCKER_GIT_PROJECTS_ROOT_HOST:-${projectsRoot}}`;
3997
+ const renderSharedCodexHostMount = (projectsRoot) => `\${DOCKER_GIT_PROJECTS_ROOT_HOST:-${projectsRoot}}/.orch/auth/codex`;
3964
3998
  const buildPlaywrightFragments = (config, networkName) => {
3965
3999
  if (!config.enableMcpPlaywright) {
3966
4000
  return {
@@ -4052,10 +4086,10 @@ ${fragments.maybePlaywrightEnv}${fragments.maybeDependsOn} env_file:
4052
4086
  - "127.0.0.1:${config.sshPort}:22"
4053
4087
  volumes:
4054
4088
  - ${config.volumeName}:/home/${config.sshUser}
4055
- - ${config.dockerGitPath}:/home/${config.sshUser}/.docker-git
4089
+ - ${renderProjectsRootHostMount(config.dockerGitPath)}:/home/${config.sshUser}/.docker-git
4056
4090
  - ${config.authorizedKeysPath}:/authorized_keys:ro
4057
4091
  - ${config.codexAuthPath}:${config.codexHome}
4058
- - ${config.codexSharedAuthPath}:${config.codexHome}-shared
4092
+ - ${renderSharedCodexHostMount(config.dockerGitPath)}:${config.codexHome}-shared
4059
4093
  - /var/run/docker.sock:/var/run/docker.sock
4060
4094
  networks:
4061
4095
  - ${fragments.networkName}
@@ -4129,8 +4163,39 @@ if [[ -z "$CDP_ENDPOINT" ]]; then
4129
4163
  CDP_ENDPOINT="http://__SERVICE_NAME__-browser:9223"
4130
4164
  fi
4131
4165
 
4166
+ # CHANGE: add retry logic for browser sidecar startup wait
4167
+ # WHY: the browser container may take time to initialize, causing MCP server to fail on first attempt
4168
+ # QUOTE(issue-123): "Почему MCP сервер лежит с ошибкой?"
4169
+ # REF: issue-123
4170
+ # SOURCE: n/a
4171
+ # FORMAT THEOREM: forall t in [1..max_attempts]: retry(t) -> eventually(cdp_ready) OR timeout_error
4172
+ # PURITY: SHELL
4173
+ # INVARIANT: script exits only after cdp_ready OR all retries exhausted
4174
+ # COMPLEXITY: O(max_attempts * timeout_per_attempt)
4175
+ MCP_PLAYWRIGHT_RETRY_ATTEMPTS="\${MCP_PLAYWRIGHT_RETRY_ATTEMPTS:-10}"
4176
+ MCP_PLAYWRIGHT_RETRY_DELAY="\${MCP_PLAYWRIGHT_RETRY_DELAY:-2}"
4177
+
4178
+ fetch_cdp_version() {
4179
+ curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version" 2>/dev/null
4180
+ }
4181
+
4182
+ JSON=""
4183
+ for attempt in $(seq 1 "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS"); do
4184
+ if JSON="$(fetch_cdp_version)"; then
4185
+ break
4186
+ fi
4187
+ if [[ "$attempt" -lt "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS" ]]; then
4188
+ echo "docker-git-playwright-mcp: waiting for browser sidecar (attempt $attempt/$MCP_PLAYWRIGHT_RETRY_ATTEMPTS)..." >&2
4189
+ sleep "$MCP_PLAYWRIGHT_RETRY_DELAY"
4190
+ fi
4191
+ done
4192
+
4193
+ if [[ -z "$JSON" ]]; then
4194
+ echo "docker-git-playwright-mcp: failed to connect to CDP endpoint $CDP_ENDPOINT after $MCP_PLAYWRIGHT_RETRY_ATTEMPTS attempts" >&2
4195
+ exit 1
4196
+ fi
4197
+
4132
4198
  # kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
4133
- JSON="$(curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version")"
4134
4199
  WS_URL="$(printf "%s" "$JSON" | node -e 'const fs=require("fs"); const j=JSON.parse(fs.readFileSync(0,"utf8")); process.stdout.write(j.webSocketDebuggerUrl || "")')"
4135
4200
  if [[ -z "$WS_URL" ]]; then
4136
4201
  echo "docker-git-playwright-mcp: webSocketDebuggerUrl missing" >&2
@@ -8465,6 +8530,8 @@ Container runtime env (set via .orch/env/project.env):
8465
8530
  DOCKER_GIT_ZSH_AUTOSUGGEST_STRATEGY=... Suggestion sources (default: history completion)
8466
8531
  MCP_PLAYWRIGHT_ISOLATED=1|0 Isolated browser contexts (recommended for many Codex; default: 1)
8467
8532
  MCP_PLAYWRIGHT_CDP_ENDPOINT=http://... Override CDP endpoint (default: http://dg-<repo>-browser:9223)
8533
+ MCP_PLAYWRIGHT_RETRY_ATTEMPTS=<n> Retry attempts for browser sidecar startup wait (default: 10)
8534
+ MCP_PLAYWRIGHT_RETRY_DELAY=<seconds> Delay between retry attempts (default: 2)
8468
8535
 
8469
8536
  Auth providers:
8470
8537
  github, gh GitHub CLI auth (tokens saved to env file)